Text file I/O is what most tutorials cover. Binary file I/O is what you need when reading image files, database records, audio samples, or any fixed-layout data format. The difference is not just the "rb" mode flag — it changes how newlines are handled, how you find positions, and how struct layout must match the file format.
Opening in Binary Mode
#include <stdio.h>
FILE *fp = fopen("data.bin", "rb"); /* read binary */
FILE *fw = fopen("out.bin", "wb"); /* write binary */
FILE *fa = fopen("log.bin", "ab"); /* append binary */On Windows, text mode translates rn to n on read and back on write. In binary mode, no translation happens — bytes are transferred exactly. Always use "rb"/"wb" for binary data, even on Linux (where there is no translation, but the intent is documented).
Writing a Struct to a File
#include <stdio.h>
#include <stdint.h>
#include <string.h>
typedef struct {
uint32_t id;
float temperature;
char label[16];
} Record;
int main() {
Record r;
r.id = 42;
r.temperature = 23.5f;
strncpy(r.label, "sensor-A", sizeof(r.label) - 1);
r.label[sizeof(r.label)-1] = '';
FILE *fp = fopen("records.bin", "wb");
if (!fp) { perror("fopen"); return 1; }
size_t written = fwrite(&r, sizeof(Record), 1, fp);
if (written != 1) { perror("fwrite"); fclose(fp); return 1; }
fclose(fp);
printf("Wrote %zu bytesn", sizeof(Record));
return 0;
}Always check fwrite‘s return value — it returns the number of items written, not bytes. If it returns fewer than requested, an error occurred.
Reading the Struct Back
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint32_t id;
float temperature;
char label[16];
} Record;
int main() {
Record r;
FILE *fp = fopen("records.bin", "rb");
if (!fp) { perror("fopen"); return 1; }
size_t n = fread(&r, sizeof(Record), 1, fp);
if (n != 1) {
if (feof(fp)) fprintf(stderr, "End of filen");
else perror("fread");
fclose(fp);
return 1;
}
fclose(fp);
printf("ID: %u, Temp: %.1f, Label: %sn", r.id, r.temperature, r.label);
return 0;
}Random Access with fseek, ftell, rewind
#include <stdio.h>
int main() {
FILE *fp = fopen("records.bin", "rb");
if (!fp) { perror("fopen"); return 1; }
/* Jump to position 8 from beginning */
fseek(fp, 8, SEEK_SET);
/* SEEK_SET = from beginning, SEEK_CUR = from current, SEEK_END = from end */
/* Get current position */
long pos = ftell(fp);
printf("Current position: %ldn", pos); /* 8 */
/* Jump to end to get file size */
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
printf("File size: %ld bytesn", file_size);
/* Reset to beginning */
rewind(fp); /* equivalent to fseek(fp, 0, SEEK_SET) + clears error flag */
fclose(fp);
return 0;
}Reading Multiple Records
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint32_t id;
float temperature;
char label[16];
} Record;
void read_record_n(FILE *fp, size_t n, Record *out) {
fseek(fp, (long)(n * sizeof(Record)), SEEK_SET);
fread(out, sizeof(Record), 1, fp);
}
int main() {
/* Write 3 records */
FILE *fw = fopen("records.bin", "wb");
Record records[3] = {
{1, 20.0f, "sensor-A"},
{2, 21.5f, "sensor-B"},
{3, 19.8f, "sensor-C"},
};
fwrite(records, sizeof(Record), 3, fw);
fclose(fw);
/* Read record 1 (second one, 0-indexed) */
FILE *fr = fopen("records.bin", "rb");
Record r;
read_record_n(fr, 1, &r);
printf("Record 1: id=%u temp=%.1f label=%sn", r.id, r.temperature, r.label);
fclose(fr);
return 0;
}Cross-Platform Pitfalls
Struct padding: The compiler may add padding between struct fields. A struct written on one platform may not read correctly on another with different alignment. Use __attribute__((packed)) or manually serialize field-by-field for cross-platform binary formats.
Endianness: An int written on a little-endian x86 machine has its bytes in a different order than on a big-endian ARM. Use htonl/ntohl or read/write byte-by-byte for portable formats.
sizeof may differ: int is 4 bytes on most 64-bit systems but was 2 bytes on 16-bit systems. Use uint32_t, int64_t, etc. from <stdint.h> for fixed-size types in binary formats.
For text file reading patterns, see our guide on reading files in C. Test binary I/O concepts in our c online compiler by writing to a buffer with fmemopen to simulate file operations without creating actual files.
TL;DR
- Always use
"rb"/"wb"for binary files — avoids newline translation on Windows - Check
fread/fwritereturn values — they return item count, not byte count fseek(fp, offset, SEEK_SET/CUR/END)jumps to a position;ftellreturns current position- Use
rewindto reset position and clear the error flag simultaneously - Use
uint32_t/int64_tin structs for cross-platform binary formats - Handle endianness explicitly if the file may be read on a different architecture
- Use
__attribute__((packed))or serialize field-by-field to eliminate padding in portable formats