Demo: Copy a File Using a Global Buffer¶
Tools: GCC, Make
Goal¶
Copy a file through a static global buffer, and see that this kind of memory needs no allocation, no release, and exists before main() is entered.
The first of four variants of the same program; the other three obtain their buffer in other ways, and nothing else about them changes.
Background¶
The program is a small cp: it reads the source file one block at a time into a buffer and writes each block to the destination.
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src)) > 0) {
size_t bytes_written = fwrite(buffer, 1, bytes_read, dst);
...
}
The loop is the same in all four variants.
What differs is the one line that brings buffer into existence, and what that line obliges the rest of the program to do.
Static global memory¶
A buffer declared at file scope:
is placed by the compiler in the BSS segment if it is zero-initialised, or the data segment if given an explicit initialiser.
BSS costs nothing on disk -- it is a note in the executable saying "reserve this much, zeroed" -- and the loader creates the area before main() runs.
There is no malloc() and no free(): the memory is simply always there, for the entire lifetime of the process.
That is its advantage and its limitation: the size is fixed when the program is compiled, and the memory is occupied whether the program uses it or not.
Build & Run¶
make
dd if=/dev/urandom of=input.bin bs=1M count=4
./copy_file input.bin output.bin
diff input.bin output.bin && echo "Files are identical"
make clean
Results and Explanations¶
Step 1 -- the global buffer¶
static at file scope means "not visible outside this translation unit", not "allocated differently" -- a plain global would live in the same place.
Being zero-initialised, it lands in BSS, and size shows it:
A megabyte of BSS, and ls -l copy_file is still about 20 KB, most of it the debug information -g adds: the buffer takes space in the running process, not in the file.
Step 2 -- writing, and the short-write check¶
size_t bytes_written = fwrite(buffer, 1, bytes_read, dst);
if (bytes_written != bytes_read) {
fprintf(stderr, "fwrite: short write\n");
goto fail_dst;
}
fwrite returns the number of items written, which is why the item size is 1 and the count is bytes_read.
Checking the result matters: a full disk reports itself here and nowhere else.
The error path does not clean up by itself: it jumps to a label at the end of main().
The labels are in the reverse order of the acquisitions, so jumping to the one that matches what is open and falling through the rest undoes exactly that.
Each failure in main() goes to the label of the last resource it holds, and the cleanup is written once.
There is nothing but two files to release in this variant -- which is precisely the contrast the malloc and mmap variants are about to draw.
References¶
man 3 fread,man 3 fwriteman 1 size-- the text/data/bss breakdown of the built program