Header <stdio.h> adalah pintu gerbang utama komunikasi program C dengan dunia luar. Hampir setiap program C yang pernah kita tulis pasti memakai printf untuk menampilkan teks ke layar atau scanf untuk membaca input dari pengguna. Selain fungsi I/O layar, <stdio.h> juga menyediakan abstraksi stream dan fungsi file I/O seperti fopen, fclose, fread, dan fwrite. Memahami header ini sangat penting karena I/O adalah salah satu sumber bug paling umum: buffer overflow, kebocoran file descriptor, dan format string yang salah. Pada chapter ini kita akan meninjau fungsi-fungsi inti <stdio.h> beserta praktik aman dalam memakainya.

Output Berformat dengan printf #

Fungsi printf menerima format string yang berisi teks biasa dan conversion specifier seperti %d, %s, %.2f. Setiap specifier akan diisi dengan argumen berikutnya sesuai urutan. Tipe argumen harus cocok dengan specifier; bila tidak cocok, behavior tidak terdefinisi (undefined behavior).

c
#include <stdio.h>

int main(void) {
    /* printf dengan beragam format */
    int umur = 25;
    double berat = 67.5;
    const char *nama = "Budi";

    printf("Nama   : %s\n", nama);
    printf("Umur   : %d tahun\n", umur);
    printf("Berat  : %.2f kg\n", berat);
    printf("Hex    : 0x%x\n", umur);
    printf("Lebar  : [%10d]\n", umur);
    printf("Kiri   : [%-10d]\n", umur);

    return 0;
}

Output yang Dihasilkan:

text
Nama   : Budi
Umur   : 25 tahun
Berat  : 67.50 kg
Hex    : 0x19
Lebar  : [        25]
Kiri   : [25        ]

Mari kita telaah baris-baris kode di atas:

  1. Baris 7 (int umur = 25;): variabel umur bertipe int dengan nilai 25. Kita akan memakai variabel ini untuk mendemokan beberapa specifier.
  2. Baris 11 (printf("Nama : %s\n", nama);): specifier %s mencetak string C-style (const char *). printf membaca byte per byte dari pointer nama sampai menemukan byte '\0'.
  3. Baris 13 (printf("Berat : %.2f kg\n", berat);): specifier %.2f membulatkan double ke dua angka di belakang koma. Tanpa .2, printf akan mencetak enam angka desimal secara default.
  4. Baris 14 (printf("Hex : 0x%x\n", umur);): %x mencetak integer dalam basis 16. Nilai 25 ditampilkan sebagai 19 karena 25 = 1*16 + 9.
  5. Baris 15 (printf("Lebar : [%10d]\n", umur);): %10d memberi field width minimum 10 karakter, rata kanan. Tambahkan - untuk rata kiri, seperti pada baris 16 (%-10d).

Membaca Input dengan fgets dan scanf #

Fungsi scanf memparses input berdasarkan format string dan menulis hasilnya ke alamat yang kita berikan. Sayangnya scanf("%s", buf) tanpa batas panjang berbahaya karena rentan buffer overflow. Untuk membaca satu baris teks utuh, fgets jauh lebih aman karena menerima parameter ukuran buffer.

c
#include <stdio.h>

int main(void) {
    char nama[64];
    int umur;

    printf("Masukkan nama: ");
    /* fgets aman dari buffer overflow - membaca sampai newline atau ukuran-1 */
    if (fgets(nama, sizeof(nama), stdin) != NULL) {
        /* Hapus newline di akhir jika ada */
        for (int i = 0; nama[i] != '\0'; i++) {
            if (nama[i] == '\n') { nama[i] = '\0'; break; }
        }
        printf("Halo, %s!\n", nama);
    }

    printf("Masukkan umur: ");
    if (scanf("%d", &umur) == 1) {
        printf("Umur Anda tahun depan: %d\n", umur + 1);
    } else {
        printf("Input umur tidak valid\n");
    }
    return 0;
}

Output yang Dihasilkan (input "Siti\n20\n"):

text
Masukkan nama: Halo, Siti!
Masukkan umur: Umur Anda tahun depan: 21

Output yang Dihasilkan (input "Joko\nbukan_angka\n"):

text
Masukkan nama: Halo, Joko!
Masukkan umur: Input umur tidak valid

Fungsi fgets selalu menulis null terminator, jadi tidak mungkin overflow buffer. Newline '\n' ikut disimpan bila muat—kita hapus manual lewat loop. Untuk scanf, return value adalah jumlah argumen yang berhasil diparse. Bila pengguna mengetik teks non-angka, return value 0 menandakan kegagalan konversi.

File I/O dengan fopen, fwrite, fread, fclose #

Stream FILE * adalah abstraksi atas file (atau perangkat). Kita membuka stream dengan fopen, melakukan operasi baca/tulis, lalu wajib menutupnya dengan fclose agar buffer ter-flush ke disk dan file descriptor dikembalikan ke sistem operasi.

c
#include <stdio.h>
#include <string.h>

int main(void) {
    const char *path = "/tmp/belajar_io.txt";
    const char *pesan = "Belajar C itu menyenangkan!\nBaris kedua di sini.\n";

    /* fopen: mode "w" = tulis (timpa), "r" = baca, "a" = append */
    FILE *fp = fopen(path, "w");
    if (fp == NULL) {
        perror("fopen gagal");
        return 1;
    }

    /* fwrite: tulis blok byte ke stream */
    size_t tertulis = fwrite(pesan, 1, strlen(pesan), fp);
    printf("Menulis %zu byte ke %s\n", tertulis, path);
    fclose(fp);

    /* Baca kembali dengan fread */
    fp = fopen(path, "r");
    if (fp == NULL) {
        perror("fopen baca gagal");
        return 1;
    }

    char buf[128];
    size_t terbaca = fread(buf, 1, sizeof(buf) - 1, fp);
    buf[terbaca] = '\0';
    printf("Membaca %zu byte:\n%s", terbaca, buf);
    fclose(fp);
    return 0;
}

Output yang Dihasilkan:

text
Menulis 49 byte ke /tmp/belajar_io.txt
Membaca 49 byte:
Belajar C itu menyenangkan!
Baris kedua di sini.

Fungsi fwrite(pesan, 1, strlen(pesan), fp) menulis strlen(pesan) elemen berukuran 1 byte dari pesan ke fp. Return value adalah jumlah elemen yang berhasil ditulis—bila kurang dari yang diminta, kemungkinan disk penuh. Pada bagian baca, kita pakai sizeof(buf) - 1 agar tersisa satu byte untuk null terminator yang kita tulis manual di buf[terbaca] = '\0'.