Secara default, setiap operasi baca/tulis memajukan file position indicator satu arah—dari awal ke akhir file. Tetapi banyak skenario yang membutuhkan akses acak: kita ingin langsung lompat ke byte ke-1000, kembali ke awal, atau mengintip ukuran file tanpa membaca seluruh isinya. Tiga fungsi yang membantu kita di sini adalah fseek untuk memindahkan posisi, ftell untuk membaca posisi saat ini, dan rewind untuk kembali ke awal. Pada chapter ini kita akan mempraktikkan pola klasik mendapatkan ukuran file dengan fseek(SEEK_END) + ftell, lalu membaca data dari posisi mana pun yang kita mau.
Kontrak fseek, ftell, rewind #
ISO/IEC 9899 §7.21.9.2 dan §7.21.9.4 mendefinisikan:
int fseek(FILE *stream, long int offset, int whence);
long int ftell(FILE *stream);
void rewind(FILE *stream);Kontrak penting:
fseekmemindahkan file position indicator — argumenoffsetbisa positif (maju) atau negatif (mundur). Argumenwhencemenentukan titik acak:SEEK_SET(awal file),SEEK_CUR(posisi saat ini), atauSEEK_END(akhir file).fseekmengembalikan0bila berhasil — nilai non-nol menandakan kegagalan (misalnya mencari ke posisi negatif).ftellmengembalikan offset byte dari awal file — tipelong(biasanya 32-bit pada sistem 32-bit, 64-bit pada sistem 64-bit). Untuk file besar, gunakanftello/fseekodengan tipeoff_t.rewindsetara denganfseek(f, 0L, SEEK_SET)ditambahclearerr— mengembalikan posisi ke awal dan menghapus flag EOF serta error.
Pola Klasik: Mendapatkan Ukuran File #
Idiom paling terkenal untuk mendapatkan ukuran file tanpa membacanya adalah:
fseek(f, 0L, SEEK_END)— lompat ke akhir file.size = ftell(f)— posisi saat ini sama dengan ukuran file.rewind(f)— kembali ke awal untuk operasi normal.
#include <stdio.h>
int main(void) {
/* Buat file dengan teks known */
FILE *f = fopen("/tmp/test_seek.txt", "w");
if (!f) { perror("fopen"); return 1; }
fputs("ABCDEFGHIJ\n", f); /* 11 byte termasuk \n */
fputs("KLMNOPQRST\n", f); /* 11 byte */
fputs("UVWXYZ01234\n", f); /* 12 byte */
fclose(f);
/* Total: 34 byte */
f = fopen("/tmp/test_seek.txt", "r");
if (!f) { perror("fopen"); return 1; }
/* Cara dapatkan ukuran file: fseek(SEEK_END) + ftell, lalu rewind */
fseek(f, 0L, SEEK_END);
long size = ftell(f);
printf("Ukuran file: %ld byte\n", size);
/* Baca dari posisi tertentu */
fseek(f, 11L, SEEK_SET); /* mulai dari baris kedua */
char buf[16] = {0};
fgets(buf, sizeof(buf), f);
printf("Dari offset 11: \"%s\"", buf);
/* SEEK_CUR: maju 5 byte dari posisi saat ini */
long pos1 = ftell(f);
fseek(f, 5L, SEEK_CUR);
long pos2 = ftell(f);
printf("Posisi sebelum: %ld, setelah SEEK_CUR+5: %ld\n", pos1, pos2);
/* Baca sisa baris */
char buf2[16] = {0};
fgets(buf2, sizeof(buf2), f);
printf("Dari offset %ld: \"%s\"", pos2, buf2);
/* rewind: kembali ke awal */
rewind(f);
printf("Setelah rewind: posisi %ld\n", ftell(f));
fclose(f);
return 0;
}Output yang Dihasilkan:
Ukuran file: 34 byte
Dari offset 11: "KLMNOPQRST
"Posisi sebelum: 22, setelah SEEK_CUR+5: 27
Dari offset 27: "Z01234
"Setelah rewind: posisi 0Perhatikan output di atas. fseek(f, 0L, SEEK_END) membawa kita ke akhir file, dan ftell melaporkan posisi 34—yang merupakan ukuran total file (11 + 11 + 12 = 34 byte). Setelah rewind, posisi kembali ke 0. Pada saat fgets membaca dari offset 11, kita mendapat "KLMNOPQRST\n" karena offset 11 adalah posisi awal baris kedua.
Mari kita telaah baris-baris kode di atas:
- Baris 22 (
fseek(f, 0L, SEEK_END);): argumenoffsetadalah0L(long zero) danwhenceadalahSEEK_END. Ini memindahkan FPI ke akhir file—tepat setelah byte terakhir. - Baris 23 (
long size = ftell(f);):ftellmengembalikan posisi saat ini. Karena kita di akhir file, posisi sama dengan jumlah byte file. - Baris 28 (
fseek(f, 11L, SEEK_SET);):SEEK_SETberarti offset dihitung dari awal file. Jadi kita lompat ke byte ke-11 (byte pertama adalahAdi posisi 0, byte ke-11 adalahK). - Baris 30 (
fgets(buf, sizeof(buf), f);): membaca dari posisi saat ini (offset 11) sampai\natausizeof(buf)-1byte. Karena baris kedua berakhir di offset 22 dengan\n,fgetsmembaca 11 byte"KLMNOPQRST\n". - Baris 35 (
fseek(f, 5L, SEEK_CUR);):SEEK_CURberarti offset relatif terhadap posisi saat ini. Setelahfgetsselesai, posisi adalah 22 (setelah\n). Maju 5 byte membawa kita ke offset 27. - Baris 41 (
rewind(f);): setara dengan(void)fseek(f, 0L, SEEK_SET)plusclearerr(f). Berguna untuk membaca file dari awal tanpa harus menutup dan membuka kembali.
Random Access pada File Biner #
Akses acak paling berguna pada file biner berisi record berukuran tetap. Bayangkan file database berisi 1000 record Mahasiswa berukuran 40 byte setiap record. Untuk membaca record ke-500, kita tidak perlu membaca 499 record sebelumnya—cukup fseek ke offset 500 * 40 = 20000.
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char nama[28];
int nilai;
} Record;
int main(void) {
/* Tulis 5 record ke file */
Record data[] = {
{0, "Nol", 10},
{1, "Satu", 20},
{2, "Dua", 30},
{3, "Tiga", 40},
{4, "Empat", 50},
};
FILE *f = fopen("/tmp/test_records.bin", "wb");
if (!f) { perror("fopen"); return 1; }
fwrite(data, sizeof(Record), 5, f);
fclose(f);
/* Baca record ke-3 (index 2) secara langsung */
f = fopen("/tmp/test_records.bin", "rb");
if (!f) { perror("fopen"); return 1; }
long offset = 2 * sizeof(Record);
fseek(f, offset, SEEK_SET);
Record rec;
fread(&rec, sizeof(Record), 1, f);
printf("Record ke-3: id=%d nama=%s nilai=%d\n",
rec.id, rec.nama, rec.nilai);
fclose(f);
/* Update record ke-3 (overwrite). Buka dengan "r+b" untuk baca+tulis */
f = fopen("/tmp/test_records.bin", "r+b");
if (!f) { perror("fopen r+b"); return 1; }
fseek(f, offset, SEEK_SET);
rec.nilai = 99;
fwrite(&rec, sizeof(Record), 1, f);
fflush(f);
/* Baca kembali untuk verifikasi */
fseek(f, offset, SEEK_SET);
Record rec2;
fread(&rec2, sizeof(Record), 1, f);
printf("Setelah update: id=%d nama=%s nilai=%d\n",
rec2.id, rec2.nama, rec2.nilai);
fclose(f);
return 0;
}Output yang Dihasilkan:
Record ke-3: id=2 nama=Dua nilai=30
Setelah update: id=2 nama=Dua nilai=99Pola fseek(offset) + fread/fwrite di atas adalah dasar implementasi database sederhana di C. Untuk membaca/menulis record ke-N, kita hitung offset sebagai N * sizeof(Record), lalu lompat langsung. Operasi ini berjalan dalam waktu konstan (O(1)) berkat dukungan sistem operasi—kita tidak perlu membaca semua record sebelumnya.
Batasan ftell dan ftello #
ftell mengembalikan tipe long yang pada beberapa sistem 32-bit hanya 32-bit—maksimum ~2 GB. Untuk file besar (> 2 GB), gunakan ftello dengan tipe off_t:
#define _FILE_OFFSET_BITS 64 /* sebelum #include */
#include <stdio.h>
#include <sys/types.h>
off_t pos = ftello(f); /* bisa sampai 8 EB bila off_t 64-bit */
fseeko(f, (off_t)1234567890, SEEK_SET);ftello dan fseeko adalah bagian dari POSIX.1-2001 dan tersedia di glibc Linux, macOS, dan sebagian besar sistem Unix modern.