Binary File Handling in C

Lecture



Working with Files in Binary Mode in C

Text files store data as text (sic!). This means that if, for example, we write the integer 12345678 to a file, 8 characters are written, which is 8 bytes of data, even though the number fits into an integer type. In addition, output and input are formatted, that is, every time we read a number from a file or write one to a file, the number is converted to a string or back. These are costly operations that can be avoided.

Text files let us store information in a form that humans can understand. However, it is also possible to store data directly in binary form. Binary files are used for this purpose.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include
#include
#include
#define ERROR_FILE_OPEN -3
void main() {
FILE *output = NULL;
int number;
output = fopen("D:/c/output.bin", "wb");
if (output == NULL) {
printf("Error opening file");
getch();
exit(ERROR_FILE_OPEN);
}
scanf("%d", &number);
fwrite(&number, sizeof(int), 1, output);
fclose(output);
_getch();
}

Run the program and look at the contents of the file output.bin. The number entered by the user is written to the file directly in binary form. You can open the file in any editor that supports hexadecimal representation (Total Commander, Far) and see this for yourself.

Writing to a file is done with the function

?
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

The function returns the number of elements successfully written. Its arguments are a pointer to an array, the size of one element, the number of elements and a pointer to the file stream. Instead of an array, any object can of course be passed.

Writing an object to a binary file is similar to displaying it: the data is taken from RAM and written as is. To read, the function fread is used

?
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

The function returns the number of elements successfully read, which are placed at the address ptr. In total, count elements of size bytes each are read. Now let us read our number back into a variable.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include
#include
#include
#define ERROR_FILE_OPEN -3
void main() {
FILE *input = NULL;
int number;
input = fopen("D:/c/output.bin", "rb");
if (input == NULL) {
printf("Error opening file");
getch();
exit(ERROR_FILE_OPEN);
}
fread(&number, sizeof(int), 1, input);
printf("%d", number);
fclose(input);
_getch();
}

fseek

One of the important functions for working with binary files is the function fseek

?
int fseek ( FILE * stream, long int offset, int origin );

This function sets the position indicator associated with the stream to a new location. The position indicator shows where in the file we have stopped. When we open a file, the position is 0. Each time we write a byte of data, the position indicator moves forward by one.
fseek takes as arguments a pointer to the stream and an offset of offset bytes relative to origin. origin can take three values

  • SEEK_SET - the beginning of the file
  • SEEK_CUR - the current position in the file
  • SEEK_END - the end of the file. Unfortunately, the standard does not define what the end of a file is, so you cannot rely on this option.

On success the function returns 0.

Let us extend our earlier example: we write a number, then move the pointer to the beginning of the file and read the number back.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include
#include
#include
#define ERROR_FILE_OPEN -3
void main() {
FILE *iofile = NULL;
int number;
iofile = fopen("D:/c/output.bin", "w+b");
if (iofile == NULL) {
printf("Error opening file");
getch();
exit(ERROR_FILE_OPEN);
}
scanf("%d", &number);
fwrite(&number, sizeof(int), 1, iofile);
fseek(iofile, 0, SEEK_SET);
number = 0;
fread(&number, sizeof(int), 1, iofile);
printf("%d", number);
fclose(iofile);
_getch();
}

Instead, you can also use the function rewind, which moves the position indicator to the beginning.

C defines a special type fpos_t, which is used to store the position of the file position indicator.
The function

?
int fgetpos ( FILE * stream, fpos_t * pos );

is used to assign the current position to the variable pos. The function

?
int fsetpos ( FILE * stream, const fpos_t * pos );

is used to move the pointer to the position stored in the variable pos. Both functions return zero on success.

?
long int ftell ( FILE * stream );

returns the current position of the indicator relative to the beginning of the file. For binary files this is the number of bytes; for text files it is undefined (if a text file consists of single-byte characters, it is also the number of bytes).

Consider an example: the user enters numbers. The first 4 bytes of the file are an integer that indicates how many numbers were entered. After the user stops entering numbers, we go back to the beginning of the file and write the number of entered elements there.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include
#include
#include
#define ERROR_OPEN_FILE -3
void main() {
FILE *iofile = NULL;
unsigned counter = 0;
int num;
int yn;
iofile = fopen("D:/c/numbers.bin", "w+b");
if (iofile == NULL) {
printf("Error opening file");
getch();
exit(ERROR_OPEN_FILE);
}
fwrite(&counter, sizeof(int), 1, iofile);
do {
printf("enter new number? [1 - yes, 2 - no]");
scanf("%d", &yn);
if (yn == 1) {
scanf("%d", &num);
fwrite(&num, sizeof(int), 1, iofile);
counter++;
} else {
rewind(iofile);
fwrite(&counter, sizeof(int), 1, iofile);
break;
}
} while(1);
fclose(iofile);
getch();
}

The second program first reads the number of stored numbers, and then reads and prints the numbers in order.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include
#include
#include
#define ERROR_OPEN_FILE -3
void main() {
FILE *iofile = NULL;
unsigned counter;
int i, num;
iofile = fopen("D:/c/numbers.bin", "rb");
if (iofile == NULL) {
printf("Error opening file");
getch();
exit(ERROR_OPEN_FILE);
}
fread(&counter, sizeof(int), 1, iofile);
for (i = 0; i < counter; i++) {
fread(&num, sizeof(int), 1, iofile);
printf("%d\n", num);
}
fclose(iofile);
getch();
}

Examples

1. There is a binary file of size 10*sizeof(int) bytes. The user enters a cell number and then writes a number into it. After each operation all the numbers are printed. First we try to open the file for reading and writing. If that fails, we try to create the file, and if the file is created successfully, we try again to open it for reading and writing.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include
#include
#include
#define SIZE 10
void main() {
const char filename[] = "D:/c/state";
FILE *bfile = NULL;
int pos;
int value = 0;
int i;
char wasCreated;
do {
wasCreated = 0;
bfile = fopen(filename, "r+b");
if (NULL == bfile) {
printf("Try to create file...\n");
getch();
bfile = fopen(filename, "wb");
if (bfile == NULL) {
printf("Error when create file");
getch();
exit(1);
}
for (i = 0; i < SIZE; i++) {
fwrite(&value, sizeof(int), 1, bfile);
}
printf("File created successfully...\n");
fclose(bfile);
wasCreated = 1;
}
} while(wasCreated);
do {
printf("Enter position [0..9] ");
scanf("%d", &pos);
if (pos < 0 || pos >= SIZE) {
break;
}
printf("Enter value ");
scanf("%d", &value);
fseek(bfile, pos*sizeof(int), SEEK_SET);
fwrite(&value, sizeof(int), 1, bfile);
rewind(bfile);
for (i = 0; i < SIZE; i++) {
fread(&value, sizeof(int), 1, bfile);
printf("%d ", value);
}
printf("\n");
} while(1);
fclose(bfile);
}

2. We write words to a binary file. The format is as follows: first the number of letters, then the word itself without the null character. If the length of a word is zero, there are no more words. First we ask the user for words, then we read them back.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include
#include
#include
#include
#define ERROR_FILE_OPEN -3
void main() {
const char filename[] = "C:/c/words.bin";
const char termWord[] = "exit";
char buffer[128];
unsigned int len;
FILE *wordsFile = NULL;
printf("Opening file...\n");
wordsFile = fopen(filename, "w+b");
if (wordsFile == NULL) {
printf("Error opening file");
getch();
exit(ERROR_FILE_OPEN);
}
printf("Enter words\n");
do {
scanf("%127s", buffer);
if (strcmp(buffer, termWord) == 0) {
len = 0;
fwrite(&len, sizeof(unsigned), 1, wordsFile);
break;
}
len = strlen(buffer);
fwrite(&len, sizeof(unsigned), 1, wordsFile);
fwrite(buffer, 1, len, wordsFile);
} while(1);
printf("rewind and read words\n");
rewind(wordsFile);
getch();
do {
fread(&len, sizeof(int), 1, wordsFile);
if (len == 0) {
break;
}
fread(buffer, 1, len, wordsFile);
buffer[len] = '\0';
printf("%s\n", buffer);
} while(1);
fclose(wordsFile);
getch();
}

3. The task is to read data from a text file and write it to a binary file. To solve it, we create a wrapper function. It will take the file name, the access mode, the function to execute if the file was opened successfully, and the arguments of that function. Since there can be many arguments and they can be of different types, they can be passed as a pointer to a structure. After the function has executed, the file is closed. This way there is no need to think about releasing resources.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include
#include
#include
#define DEBUG
#ifdef DEBUG
#define debug(data) printf("%s", data);
#else
#define debug(data)
#endif
const char inputFile[] = "D:/c/xinput.txt";
const char outputFile[] = "D:/c/output.bin";
struct someArgs {
int* items;
size_t number;
};
int writeToFile(FILE *file, void* args) {
size_t i;
struct someArgs *data = (struct someArgs*) args;
debug("write to file\n")
fwrite(data->items, sizeof(int), data->number, file);
debug("write finished\n")
return 0;
}
int readAndCallback(FILE *file, void* args) {
struct someArgs data;
size_t size, i = 0;
int result;
debug("read from file\n")
fscanf(file, "%d", &size);
data.items = (int*) malloc(size*sizeof(int));
data.number = size;
while (!feof(file)) {
fscanf(file, "%d", &data.items[i]);
i++;
}
debug("call withOpenFile\n")
result = withOpenFile(outputFile, "w", writeToFile, &data);
debug("read finish\n")
free(data.items);
return result;
}
int doStuff() {
return withOpenFile(inputFile, "r", readAndCallback, NULL);
}
//Wrapper - the function opens a file. If the file was opened successfully,
//the function fun is called. Since the arguments can be quite varied,
//they are passed through a void* pointer. It is reasonable to use
//a structure as the argument type
int withOpenFile(const char *filename,
const char *mode,
int (*fun)(FILE* source, void* args),
void* args) {
FILE *file = fopen(filename, mode);
int err;
debug("try to open file ")
debug(filename)
debug("\n")
if (file != NULL) {
err = fun(file, args);
} else {
return 1;
}
debug("close file ")
debug(filename)
debug("\n")
fclose(file);
return err;
}
void main() {
printf("result = %d", doStuff());
getch();
}

4. The saveInt32Array function saves an array of type int32_t to a file. Its counterpart, loadInt32Array, reads the array back. The loadInt32Array function first allocates the array passed to it, so we have to pass a pointer to a pointer; in addition, it writes the size of the array that was read into the size parameter, which is why that parameter is passed as a pointer.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include
#include
#include
#include
#define SIZE 100
int saveInt32Array(const char *filename, const int32_t *a, size_t size) {
FILE *out = fopen(filename, "wb");
if (!out) {
return 0;
}
//Write the array length
fwrite(&size, sizeof(size_t), 1, out);
//Write the whole array
fwrite(a, sizeof(int32_t), size, out);
fclose(out);
return 1;
}
int loadInt32Array(const char *filename, int32_t **a, size_t *size) {
FILE *in = fopen(filename, "rb");
if (!in) {
return 0;
}
//Read the array length
fread(size, sizeof(size_t), 1, in);
//Allocate the array
(*a) = (int32_t*) malloc(sizeof(int32_t) * (*size));
if (!(*a)) {
return 0;
}
//Read the whole array
fread((*a), sizeof(int32_t), *size, in);
fclose(in);
return 1;
}
void main() {
const char *tmpFilename = "tmp.bin";
int32_t exOut[SIZE];
int32_t *exIn = NULL;
size_t realSize;
int i;
for (i = 0; i < SIZE; i++) {
exOut[i] = i*i;
}
saveInt32Array(tmpFilename, exOut, SIZE);
loadInt32Array(tmpFilename, &exIn, &realSize);
for (i = 0; i < realSize; i++) {
printf("%d ", exIn[i]);
}
_getch();
}

5. Creating a lookup table. To speed up a program, instead of computing a function each time, you can first compute the function values over an interval with a given precision and then take the values from the table. The program first tabulates the function with the given parameters and saves the result to a file, then loads the precomputed array, which is then used to look up values. In this program every function returns a variable of type Result, which holds an error code. If the function ran without problems, it returns Ok (0).

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#define _CRT_SECURE_NO_WARNINGS
//Yes, adding this is now mandatory, otherwise it will not work
#include
#include
#include
#include
#include
//Each function returns a result. If it equals Ok, the function
//ran without problems
typedef int Result;
//Possible results
#define Ok 0
#define ERROR_OPENING_FILE 1
#define ERROR_OUT_OF_MEMORY 2
//The function that we will tabulate
double mySinus(double x) {
return sin(x);
}
Result tabFunction(const char *filename, double from, double to, double step, double (*f)(double)) {
Result r;
FILE *out = fopen(filename, "wb");
double value;
if (!out) {
r = ERROR_OPENING_FILE;
goto EXIT;
}
fwrite(&from, sizeof(from), 1, out);
fwrite(&to, sizeof(to), 1, out);
fwrite(&step, sizeof(step), 1, out);
for (from; from < to; from += step) {
value = f(from);
fwrite(&value, sizeof(double), 1, out);
}
r = Ok;
EXIT:
fclose(out);
return r;
}
Result loadFunction(const char *filename, double **a, double *from, double *to, double *step) {
Result r;
uintptr_t size;
FILE *in = fopen(filename, "rb");
if (!in) {
r = ERROR_OPENING_FILE;
goto EXIT;
}
//Read the auxiliary information
fread(from, sizeof(*from), 1, in);
fread(to, sizeof(*to), 1, in);
fread(step, sizeof(*step), 1, in);
//Allocate the array
size = (uintptr_t) ((*to - *from) / *step);
(*a) = (double*) malloc(sizeof(double)* size);
if (!(*a)) {
r = ERROR_OUT_OF_MEMORY;
goto EXIT;
}
//Read the whole array
fread((*a), sizeof(double), size, in);
r = Ok;
EXIT:
fclose(in);
return r;
}
void main() {
const char *tmpFilename = "tmp.bin";
Result r;
double *exIn = NULL;
int accuracy, option;
double from, to, step, arg;
uintptr_t index;
//Ask for the parameters used to build the lookup table
printf("Enter parameters\nfrom = ");
scanf("%lf", &from);
printf("to = ");
scanf("%lf", &to);
printf("step = ");
scanf("%lf", &step);
r = tabFunction(tmpFilename, from, to, step, mySinus);
if (r != Ok) {
goto CATCH_SAVE_FUNCTION;
}
//Note the output format. The precision is determined
//at run time. The * format specifier will substitute the precision value,
//taking it from the argument list
accuracy = (int) (-log10(step));
printf("function tabulated from %.*lf to %.*lf with accuracy %.*lf\n",
accuracy, from, accuracy, to, accuracy, step);
r = loadFunction(tmpFilename, &exIn, &from, &to, &step);
if (r != Ok) {
goto CATCH_LOAD_FUNCTION;
}
accuracy = (int)(-log10(step));
do {
printf("1 to enter values, 0 to exit : ");
scanf("%d", &option);
if (option == 0) {
break;
}
else if (option != 1) {
continue;
}
printf("Enter value from %.*lf to %.*lf : ", accuracy, from, accuracy, to);
scanf("%lf", &arg);
if (arg < from || arg > to) {
printf("bad value\n");
continue;
}
index = (uintptr_t) ((arg - from) / step);
printf("saved %.*lf\ncomputed %.*lf\n", accuracy, exIn[index], accuracy, mySinus(arg));
} while (1);
r = Ok;
goto EXIT;
CATCH_SAVE_FUNCTION: {
printf("Error while saving values");
goto EXIT;
}
CATCH_LOAD_FUNCTION: {
printf("Error while loading values");
goto EXIT;
}
EXIT:
free(exIn);
_getch();
exit(r);
}

6. We have two structures. The first, PersonKey, stores the login, password, user id and an offset field. The second structure, PersonInfo, stores the user's first and last name and age. The first structures are written to the binary file keys.bin, and the second ones to the binary file values.bin. The offset field determines the position of the corresponding user information in the second file. Thus, having obtained a PersonKey from the first file, you can use the offset field to extract from the second file the information associated with that key.

Why do it this way? It pays off when the PersonInfo structure is large. Extracting an array of small structures from a file is cheap, and when we need a large structure, we can extract it from the already known address in the file.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
typedef struct PersonKey {
long long id;
char login[64];
char password[64];
long offset;//Position of the corresponding PersonInfo values
} PersonKey;
typedef struct PersonInfo {
unsigned age;
char firstName[64];
char lastName[128];
} PersonInfo;
/*
Function asks the user for data and writes it one after another into two files
*/
void createOnePerson(FILE *keys, FILE *values) {
static long long id = 0;
PersonKey pkey;
PersonInfo pinfo;
pkey.id = id++;
//Since all values are written one after another, the current position
//of the pointer in the second file will be the position for the new record
pkey.offset = ftell(values);
printf("Login: ");
scanf("%63s", pkey.login);
printf("Password: ");
scanf("%63s", pkey.password);
printf("Age: ");
scanf("%d", &(pinfo.age));
printf("First Name: ");
scanf("%63s", pinfo.firstName);
printf("Last Name: ");
scanf("%127s", pinfo.lastName);
fwrite(&pkey, sizeof(pkey), 1, keys);
fwrite(&pinfo, sizeof(pinfo), 1, values);
}
void createPersons(FILE *keys, FILE *values) {
char buffer ;
int repeat = 1;
int counter = 0;//Number of elements in the file
//Reserve space for writing the number of elements
fwrite(&counter, sizeof(counter), 1, keys);
printf("CREATE PERSONS\n");
do {
createOnePerson(keys, values);
printf("\nYet another one? [y/n]");
scanf("%1s", buffer);
counter++;
if (buffer != 'y' && buffer != 'Y') {
repeat = 0;
}
} while(repeat);
//Go back to the start and write the number of created elements
rewind(keys);
fwrite(&counter, sizeof(counter), 1, keys);
}
/*
Create the array of keys
*/
PersonKey* readKeys(FILE *keys, int *size) {
int i;
PersonKey *out = NULL;
rewind(keys);
fread(size, sizeof(*size), 1, keys);
out = (PersonKey*) malloc(*size * sizeof(PersonKey));
fread(out, sizeof(PersonKey), *size, keys);
return out;
}
/*
Function opens two files at once. To simplify the task, we return an array of files.
*/
FILE** openFiles(const char *keysFilename, const char *valuesFilename) {
FILE **files = (FILE**)malloc(sizeof(FILE*)*2);
files = fopen(keysFilename, "w+b");
if (!files ) {
return NULL;
}
files = fopen(valuesFilename, "w+b");
if (!files ) {
fclose(files );
return NULL;
}
return files;
}
/*
Two helper functions for printing a key and the info
*/
void printKey(PersonKey pk) {
printf("%d. %s [%s]\n", (int)pk.id, pk.login, pk.password);
}
void printInfo(PersonInfo info) {
printf("%d %s %s\n", info.age, info.firstName, info.lastName);
}
/*
Function takes the needed value from the second file
using a key (more precisely, its offset field)
*/
PersonInfo readInfoByPersonKey(PersonKey pk, FILE *values) {
PersonInfo out;
rewind(values);
fseek(values, pk.offset, SEEK_SET);
fread(&out, sizeof(PersonInfo), 1, values);
return out;
}
void getPersonsInfo(PersonKey *keys, FILE *values, int size) {
int index;
PersonInfo p;
do {
printf("Enter position of element. To exit print bad index: ");
scanf("%d", &index);
if (index < 0 || index >= size) {
printf("Bad index");
return;
}
p = readInfoByPersonKey(keys[index], values);
printInfo(p);
} while (1);
}
void main() {
int size;
int i;
PersonKey *keys = NULL;
FILE **files = openFiles("C:/c/keys.bin", "C:/c/values.bin");
if (files == 0) {
printf("Error opening files");
goto FREE;
}
createPersons(files , files );
keys = readKeys(files , &size);
for (i = 0; i < size; i++) {
printKey(keys[i]);
}
getPersonsInfo(keys, files , size);
fclose(files );
fclose(files );
FREE:
free(files);
free(keys);
_getch();
}

See also

  • [[b9350]]

See also

created: 2021-03-13
updated: 2026-09-29
154



Was this answer useful?
Choose a quick rating so we can improve the next answer for you.
How satisfied are you?


Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Algorithmization and programming. Structural programming. C language"

Terms: Algorithmization and programming. Structural programming. C language