This episode masters C input-output: printf, scanf, getchar, and putchar for standard I/O, then fopen, fread, fwrite, fprintf, fgets, and fclose for file handling. You will also understand file modes and error handling with errno and perror.

A program that doesn't communicate with the outside world is nearly useless. Episode 8 covers input-output: how a program reads from the keyboard and writes to the screen, and how to read and write files on disk.
Standard I/O works through streams: stdin for input, stdout and stderr for output. Files are accessed through FILE *, a structure opened with fopen and always closed with fclose.
In the real world, I/O is the biggest source of errors: missing files, denied permissions, or a full disk. That's why this episode also covers error handling with errno and perror — a skill you'll keep using in episode 12 when handling network I/O.
The printf function is already familiar. The format string determines how values are formatted: %d for int, %f for double, %s for strings, and %zu for size_t. A format string can contain spaces, text, and escapes like \n for a newline.
The scanf function reads formatted input. Because C uses passing by value, scanf needs the address of the target variable through &:
cat > input.c <<'EOF'
#include <stdio.h>
int main(void) {
char nama[64];
int umur;
printf("Nama: ");
fgets(nama, sizeof(nama), stdin);
printf("Umur: ");
if (scanf("%d", &umur) != 1) {
return 1;
}
printf("Halo %sumur %d\n", nama, umur);
return 0;
}
EOF
gcc -Wall -Wextra input.c -o input && ./inputThe pattern scanf("%d", &umur) writes the result to umur's address and returns the number of items successfully read. Check this return value; scanf returning anything other than 1 means the input didn't match. For text lines, fgets is safer than scanf because it bounds the buffer length.
To read and write a single character, use getchar and putchar. Both are useful for processing text character by character without format buffering:
int c;
while ((c = getchar()) != EOF) {
putchar(c);
}The pattern while ((c = getchar()) != EOF) reads until the end of input. Note that c is typed int, not char, so it can hold EOF, which is negative.
Files are opened with fopen, which takes a file name and a mode. Every opened file must be closed with fclose:
cat > tulis_file.c <<'EOF'
#include <stdio.h>
int main(void) {
FILE *f = fopen("catatan.txt", "w");
if (f == NULL) {
perror("fopen");
return 1;
}
fprintf(f, "Baris pertama\n");
fprintf(f, "Baris kedua\n");
fclose(f);
return 0;
}
EOF
gcc -Wall -Wextra tulis_file.c -o tulis_file && ./tulis_filefopen("catatan.txt", "w") opens the file in write mode, overwriting old contents. Always check the result of fopen; a NULL value means the open failed. fprintf works like printf but writes to a file, and fclose flushes the remaining buffer to disk.
To read text line by line, use fgets. For binary data, use fread and fwrite, which read or write blocks of bytes:
cat > baca_file.c <<'EOF'
#include <stdio.h>
int main(void) {
FILE *f = fopen("catatan.txt", "r");
char baris[256];
if (f == NULL) {
perror("fopen");
return 1;
}
while (fgets(baris, sizeof(baris), f) != NULL) {
printf("%s", baris);
}
fclose(f);
return 0;
}
EOF
gcc -Wall -Wextra baca_file.c -o baca_file && ./baca_fileThe loop while (fgets(baris, sizeof(baris), f) != NULL) reads until the end of the file, when fgets returns NULL. The size sizeof(baris) bounds the number of characters read so no buffer overflow occurs.
The mode parameter determines which operations are allowed:
"r": read, the file must already exist."w": write, overwrites or creates new."a": append at the end, creates new if it doesn't exist."rb" and "wb": read and write in binary mode."r+", "w+", "a+": read and write combinations.Binary mode matters for non-text data because Windows handles newlines differently than Linux. On Linux, text and binary modes are identical.
The ftell and fseek functions read and change the read-write position within a file. fseek(f, offset, SEEK_SET) moves the position to an offset from the start of the file, and rewind returns the position to the beginning. This is useful for accessing binary data at a specific offset, such as a file format's header.
When an I/O function fails, the global errno variable is set to an error code, and perror prints a human-readable message:
cat > error_io.c <<'EOF'
#include <stdio.h>
#include <errno.h>
int main(void) {
FILE *f = fopen("tidak-ada.txt", "r");
if (f == NULL) {
perror("Gagal membuka file");
return 1;
}
fclose(f);
return 0;
}
EOF
gcc -Wall -Wextra error_io.c -o error_io && ./error_ioperror("Gagal membuka file") prints your message followed by the error description like "No such file or directory". errno can be read directly to handle different cases separately.
Check the return value of every I/O operation: fopen, fgets, fread, fwrite, and fclose can all fail. Don't write code that assumes I/O always succeeds. An error caught early is far cheaper than a crash in production.
Tip
Clean up resources on both the success and failure paths. A common pattern is opening a file, processing, and ensuring fclose is called before every return — including when an error occurs.
Key takeaways:
In the next episode 9 we will discuss basic data structures and algorithms — linked lists, stacks, and queues, using arrays and pointers in data structures, basic bubble, insertion, and selection sort, and linear and binary searching with simple complexity analysis.