Question
I am writing a C program that opens a file, and I need to determine the file's size.
I want to know the size because I plan to read the file's contents into a dynamically allocated character buffer using malloc().
Using a fixed allocation such as the following seems like a poor approach:
char *buffer = malloc(10000 * sizeof(char));
How can I correctly find the size of a file in C before allocating memory for its contents?
Short Answer
By the end of this page, you will understand how to determine a file's size in C, why that matters before calling malloc(), and how to safely read a whole file into memory. You will also see common mistakes, practical patterns, and a complete mini project you can reuse.
Concept
In C, file size is often needed when you want to read an entire file into memory at once. A common beginner pattern is:
- Open the file
- Find its size
- Allocate enough memory with
malloc() - Read the file into the buffer
- Add a null terminator if you want to treat it as a C string
A common way to get the size of a regular file is to move the file position to the end using fseek(), then ask for the current position with ftell(). That position is typically the file size in bytes.
Basic idea:
fseek(file, 0, SEEK_END);
long size = ftell(file);
rewind(file);
Why this matters:
- It helps you allocate the correct amount of memory.
- It avoids hardcoded buffer sizes.
- It reduces the risk of buffer overflows or wasted memory.
- It makes file-reading code more reliable.
Important caveat: this approach works well for regular files, but not all streams behave the same way. For example, pipes, terminals, or some special devices may not support seeking. For normal disk files, it is a standard beginner-friendly solution.
Mental Model
Think of a file like a book with a bookmark.
fseek()moves the bookmark.ftell()tells you where the bookmark currently is.- If you move the bookmark to the end of the book, the bookmark's position tells you how many bytes are in the file.
- Then you move the bookmark back to the beginning and start reading.
So the process is:
- Jump to the end
- Check the position
- Jump back to the start
- Read exactly that many bytes
Syntax and Examples
The classic pattern in C uses fopen(), fseek(), ftell(), and rewind().
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *file = fopen("example.txt", "rb");
if (file == NULL) {
perror("fopen");
return 1;
}
if (fseek(file, 0, SEEK_END) != 0) {
perror("fseek");
fclose(file);
return 1;
}
long size = ftell(file);
if (size < 0) {
perror("ftell");
fclose(file);
return 1;
}
rewind(file);
char *buffer = malloc((size_t)size + 1);
if (buffer == NULL) {
perror("malloc");
fclose(file);
return 1;
}
size_t bytes_read = fread(buffer, 1, (size_t)size, file);
if (bytes_read != (size_t)size) {
perror("fread");
free(buffer);
fclose(file);
return 1;
}
buffer[size] = '\0';
printf("File size: %ld bytes\n", size);
printf("Content:\n%s\n", buffer);
free(buffer);
fclose(file);
return 0;
}
What this example does
- Opens the file in binary mode with
"rb" - Moves to the end of the file
- Uses
ftell()to get the size - Returns to the beginning with
rewind() - Allocates
size + 1bytes so the content can be null-terminated - Reads the whole file into memory
- Prints the size and contents
Why + 1?
If you want to treat the file content as a C string, you need one extra byte for \0.
Why use "rb"?
Binary mode avoids text translation on some systems. That makes byte counts more predictable when you care about exact file size.
Step by Step Execution
Consider this file content:
Hi
That file contains 2 bytes if it is exactly H and i, or 3 bytes if it includes a newline. Suppose the file contains Hi only.
Now trace this code:
FILE *file = fopen("example.txt", "rb");
fseek(file, 0, SEEK_END);
long size = ftell(file);
rewind(file);
char *buffer = malloc((size_t)size + 1);
size_t bytes_read = fread(buffer, 1, (size_t)size, file);
buffer[size] = '\0';
Step by step:
-
fopen("example.txt", "rb")- Opens the file for reading.
- The file position starts at the beginning.
-
fseek(file, 0, SEEK_END)- Moves the file position to the end.
-
long size = ftell(file)- Gets the current position.
- If the file has 2 bytes,
sizebecomes2.
-
rewind(file)- Moves the file position back to the start.
-
malloc((size_t)size + 1)- Allocates 3 bytes: 2 for file data and 1 for
\0.
- Allocates 3 bytes: 2 for file data and 1 for
-
fread(buffer, 1, (size_t)size, file)- Reads 2 bytes into
buffer. buffer[0] = 'H'buffer[1] = 'i'
- Reads 2 bytes into
-
buffer[size] = '\0'buffer[2] = '\0'- Now
bufferis a valid C string:"Hi"
This is the key pattern for reading a whole regular file into memory safely.
Real World Use Cases
Getting a file's size is useful in many practical programs:
-
Loading configuration files
- Read a JSON, text, or INI file into memory before parsing it.
-
Reading templates or static assets
- Server-side programs may load HTML templates or email bodies.
-
Processing small data files
- Import CSV or log files for parsing.
-
Copying file contents
- Allocate a buffer large enough for the whole file.
-
Testing and tooling
- Read fixture files in unit tests.
-
Binary file handling
- Load images, custom formats, or serialized data where exact byte count matters.
This pattern is most appropriate when the file is small enough to fit comfortably in memory. For very large files, developers usually read in chunks instead.
Real Codebase Usage
In real projects, developers usually wrap this logic in a helper function instead of repeating it everywhere.
Common patterns include:
Guard clauses
Return early if something fails:
if (file == NULL) return NULL;
if (fseek(file, 0, SEEK_END) != 0) return NULL;
This keeps code flatter and easier to read.
Validation before allocation
Check that ftell() did not fail before converting to size_t:
long size = ftell(file);
if (size < 0) {
fclose(file);
return NULL;
}
Reading whole files into a reusable utility
Many codebases use a helper like:
char *read_file(const char *path, long *out_size);
That helper can:
- open the file
- get the size
- allocate memory
- read contents
- return both buffer and size
Error handling
Real programs must handle:
- file not found
- permission denied
- allocation failure
- short reads
- non-seekable input
Chunked reading for large inputs
Even if file-size lookup is available, production code may prefer chunked reading to avoid large allocations or to support streams.
Common Mistakes
Here are common beginner mistakes when working with file sizes in C.
1. Forgetting to go back to the start before reading
Broken code:
fseek(file, 0, SEEK_END);
long size = ftell(file);
char *buffer = malloc((size_t)size + 1);
fread(buffer, 1, (size_t)size, file);
Problem:
- The file position is still at the end.
fread()reads zero bytes.
Fix:
rewind(file);
2. Not checking whether ftell() failed
Broken code:
long size = ftell(file);
char *buffer = malloc((size_t)size + 1);
Problem:
ftell()returns-1Lon error.- Converting that to
size_tcan create a huge number.
Fix:
if (size < 0) {
// handle error
}
3. Forgetting space for the null terminator
Broken code:
char *buffer = malloc((size_t)size);
buffer[size] = '\0';
Problem:
- Writing
buffer[size]goes past the allocated memory.
Fix:
char *buffer = malloc((size_t)size + 1);
4. Assuming every file can be treated as text
If the file is binary data, adding \0 does not make it meaningful as a string.
Use the size separately:
void *buffer = malloc((size_t)size);
5. Ignoring the return value of fread()
Broken code:
fread(buffer, 1, (size_t)size, file);
Problem:
- Reads can fail or be shorter than expected.
Fix:
size_t bytes_read = fread(buffer, 1, (size_t)size, file);
if (bytes_read != (size_t)size) {
// handle error
}
6. Using a fixed-size buffer for unknown file sizes
Broken code:
char *buffer = malloc(10000);
Problem:
- Too small for large files
- Wasteful for tiny files
- Fragile and hard to maintain
Fix:
- Measure the file first, or read in chunks.
Comparisons
| Approach | How it works | Good for | Limitations |
|---|---|---|---|
fseek() + ftell() | Move to end, ask for current position | Regular files, simple programs | Not suitable for all streams |
| Read in chunks | Repeatedly call fread() into a smaller buffer | Large files, streams, unknown sizes | Slightly more code |
| Fixed-size buffer | Allocate a guessed amount | Quick experiments only | Unsafe and unreliable |
rewind() vs fseek(file, 0, SEEK_SET)
| Function | Purpose | Notes |
|---|---|---|
rewind(file) | Move back to the beginning | Simple and readable |
fseek(file, 0, SEEK_SET) | Move to byte 0 from the start | More general-purpose |
Text mode vs binary mode
| Mode | Meaning | When to use |
|---|---|---|
"r" | Read text file | Simple text input when exact byte handling is not critical |
"rb" | Read binary file | Safer when you care about exact bytes or portability |
Cheat Sheet
FILE *file = fopen("example.txt", "rb");
if (file == NULL) {
// handle error
}
if (fseek(file, 0, SEEK_END) != 0) {
// handle error
}
long size = ftell(file);
if (size < 0) {
// handle error
}
rewind(file);
char *buffer = malloc((size_t)size + 1);
if (buffer == NULL) {
// handle error
}
size_t bytes_read = fread(buffer, 1, (size_t)size, file);
if (bytes_read != (size_t)size) {
// handle error
}
buffer[size] = '\0';
Key rules
- Use
fseek(file, 0, SEEK_END)to move to the end. - Use
ftell(file)to get the position in bytes. - Check if
ftell()returned a negative value. - Use
rewind(file)before reading. - Allocate
size + 1if you need a C string. - Check the return values of
fopen(),fseek(),ftell(),malloc(), andfread(). - Use chunked reading for large files or non-seekable input.
Edge cases
- Empty file: size is
0 - Binary file: content may contain
\0bytes - Non-seekable stream:
fseek()may fail
FAQ
How do I get a file's size in C?
A common method is to use fseek(file, 0, SEEK_END), then call ftell(file), and finally return to the beginning with rewind(file).
Why should I not use a fixed malloc(10000) buffer?
Because the file may be larger than 10,000 bytes or much smaller. Fixed guesses are unreliable and can waste memory or cause bugs.
Do I need to add 1 when allocating memory for a file?
Yes, if you want to treat the data as a C string. The extra byte stores the null terminator \0.
What happens if ftell() returns -1?
That means an error occurred. You should not pass that value to malloc().
Can I use this method for binary files?
Yes. The size still represents the number of bytes. But binary data should not automatically be treated as text.
Does this work for every kind of input stream?
No. Some streams, such as pipes or terminals, may not support seeking.
Is sizeof(char) necessary in malloc()?
No. sizeof(char) is always 1, so malloc(size) is enough for a character buffer.
What should I do for very large files?
Read the file in chunks instead of loading the entire file into memory at once.
Mini Project
Description
Build a small C program that reads an entire text file into memory and prints both its size and contents. This demonstrates the full workflow of opening a file, measuring it safely, allocating memory dynamically, reading data, and cleaning up resources properly.
Goal
Create a program that loads a file into a dynamically allocated buffer using the file's actual size.
Requirements
- Open a file from a path provided in the code.
- Determine the file size before allocation.
- Allocate enough memory for the file contents plus a null terminator.
- Read the entire file into memory.
- Print the file size and contents, then free the memory and close the file.
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.