Copying a file in C demonstrates how to combine file I/O functions (fopen, fgetc, fputc, fclose) with command-line arguments to build a practical utility. The program reads filenames from argv, opens both files, copies byte-by-byte, and reports how many bytes were transferred.
The original version used conio.h, process.h, void main(), and stored the return value of fgetc() in a char — a subtle bug: fgetc() returns int so it can represent EOF (−1) distinctly from any valid byte. Storing it in a char can cause EOF to be misidentified on platforms where char is unsigned.
C Program to Copy File Contents
/* Copy file contents from source to destination
* Usage: ./filecopy source.txt dest.txt
* Compile: gcc -ansi -Wall -Wextra filecopy.c -o filecopy */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE *src, *dst;
int ch; /* int, not char — fgetc returns int so EOF (-1) works */
long count = 0;
if (argc != 3) {
fprintf(stderr, "Usage: %s source destination\n", argv[0]);
return 1;
}
src = fopen(argv[1], "r");
if (src == NULL) {
fprintf(stderr, "Error: cannot open source file '%s'\n", argv[1]);
return 1;
}
dst = fopen(argv[2], "w");
if (dst == NULL) {
fprintf(stderr, "Error: cannot open destination file '%s'\n", argv[2]);
fclose(src);
return 1;
}
while ((ch = fgetc(src)) != EOF) {
fputc(ch, dst);
count++;
}
fclose(src);
fclose(dst);
printf("Copied %ld bytes from '%s' to '%s'.\n", count, argv[1], argv[2]);
return 0;
}
How to Compile and Run
gcc -ansi -Wall -Wextra filecopy.c -o filecopy
echo "Hello from source" > source.txt
./filecopy source.txt destination.txt
Sample Output
# Success: ./filecopy source.txt destination.txt Copied 19 bytes from 'source.txt' to 'destination.txt'. # Wrong number of arguments: ./filecopy Usage: ./filecopy source destination # Source file missing: ./filecopy missing.txt out.txt Error: cannot open source file 'missing.txt'
How the Copy Loop Works
while ((ch = fgetc(src)) != EOF) {
fputc(ch, dst);
count++;
}
fgetc(src)reads one byte from the source file and returns it as anint.- The return value is assigned to
chinside the while condition — the assignment happens before the comparison. - If
ch == EOF, the loop ends. EOF is a negative constant (typically −1) that signals end of file or read error. fputc(ch, dst)writes the byte to the destination file.
The outer parentheses around (ch = fgetc(src)) are required — without them, the expression parses as ch = (fgetc(src) != EOF), which stores 0 or 1 instead of the character.
Code Explanation
int ch(notchar ch) —fgetc()is declared as returningintso it can return any valid byte value (0–255) AND the special EOF marker (−1) without collision. If you store the result in acharandcharis unsigned on your platform, EOF (−1 wrapped to 255) will be indistinguishable from the byte value 255, causing an infinite loop on binary files containing 0xFF bytes.- Open mode
"r"and"w"— text mode on the platform. For binary files (images, executables), use"rb"and"wb"to prevent newline translation on Windows. - fclose on error path — when the destination cannot be opened,
fclose(src)is called before returning. Failing to close a file on error paths leaks the file descriptor — harmless in a short program but important in long-running ones. - fprintf to stderr — error messages go to stderr, not stdout. This lets the caller distinguish program output from error messages and allows shell redirection:
./filecopy src dst 2>errors.log. - Byte count with
long— usinglonginstead ofintallows reporting file sizes up to ~2 billion bytes on 32-bit platforms. For large files,long longorsize_twould be used.
What This Program Teaches
- File I/O with fopen/fgetc/fputc/fclose — the foundational file API in C. Every file operation follows the same pattern: open → read/write in a loop → close. Always check the return value of fopen for NULL.
- The fgetc int vs char distinction — one of the most common C bugs in file-reading code. The C standard explicitly requires fgetc to return int. The idiom
int ch; while ((ch = fgetc(fp)) != EOF)is the correct pattern. - Command-line argument validation — any utility-style program should validate argc and print a usage message. Using
argv[0]in the usage message keeps it correct if the binary is renamed. - Resource cleanup on all exit paths — a good habit: every fopen that succeeds must have a matching fclose on every return path, including error paths.
Related Programs
Recommended book:
The C Programming Language — Kernighan & Ritchie (India) |
(US)
|
C Programming: A Modern Approach — K.N. King (India) |
(US)
Practice what you learned: C Aptitude Questions — or try our C Programming Quiz App on Android.