Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 18.122 phút đọc

Luồng và kiểu FILE

Sau bài này bạn sẽ làm được

  • Giải thích FILE là kiểu mờ và vì sao không được tự tạo đối tượng FILE
  • Phân biệt ba chế độ đệm và biết chế độ mặc định của từng luồng
  • Chỉ ra vì sao printf không hiện gì khi chương trình sập
  • Dùng setvbuf và fflush đúng lúc

Mọi chương trình từ Phần 0 tới giờ đều đã đọc ghi file mà bạn không để ý: printf ghi vào một file, scanf đọc từ một file. Bài này mở nắp xem bên dưới có gì, và giải thích một hiện tượng đã làm phiền bạn nhiều lần mà chưa có lời giải.

#Luồng là gì

Luồng
Một dãy byte có thứ tự, đọc hoặc ghi tuần tự từ đầu tới cuối. Thư viện chuẩn C không phân biệt byte đó tới từ đâu: file trên đĩa, bàn phím, màn hình, máy in hay một chương trình khác đều là luồng.
Chuong trinh cua ban
        |
        | fputs, fprintf, fputc      <- cung mot bo ham
        v
   [   LUONG   ]
        |
        +--> tep tren dia
        +--> man hinh
        +--> may in
        +--> mot chuong trinh khac (ong dan)
        +--> ket noi mang (tren Linux)

#Kiểu FILE là kiểu mờ

#include <stdio.h>

FILE *f;                    /* CON TRO toi FILE, khong bao gio la FILE */
Kiểu mờ
Kiểu mà bạn biết tên nhưng không biết bên trong có gì. Thư viện chỉ cho bạn con trỏ tới nó và một bộ hàm để thao tác. Bạn không được đọc trường, không được sao chép, không được tự tạo.
Đụng vào ruột FILE
FILE f;                          /* khong the: kich thuoc va noi dung
                                    la chuyen rieng cua thu vien */

FILE *g = fopen("a.txt", "r");
FILE  h = *g;                    /* SAO CHEP: hong.
                                    Hai doi tuong cung tro toi mot bo dem,
                                    dong mot cai la cai kia thanh rac. */

printf("%d\n", g->_flags);       /* KHONG KHA CHUYEN: ten truong
                                    khac nhau giua cac thu vien */
Chỉ dùng con trỏ và các hàm
FILE *f = fopen("a.txt", "r");   /* chi giu con tro */

if (feof(f))   { /* ... */ }     /* hoi trang thai bang HAM */
if (ferror(f)) { /* ... */ }
long vt = ftell(f);              /* hoi vi tri bang HAM */

fclose(f);                       /* tra lai cho thu vien */

Bên trong nó có gì

Bạn không cần biết, nhưng biết thì hiểu được vì sao các hàm hành xử như vậy. Mọi cài đặt đều giữ ít nhất bốn thứ sau.

ThứDùng để làm gìHàm nào chạm tới
Bộ đệmGom nhiều lần ghi nhỏ thành một lần ghi lớnfflush, setvbuf
Vị trí hiện tạiLần đọc ghi tiếp theo bắt đầu từ đâufseek, ftell, rewind
Cờ kết thúc và cờ lỗiNhớ rằng lần đọc trước đã thất bạifeof, ferror, clearerr
Số hiệu file của hệ điều hànhCầu nối xuống nhân hệ điều hànhfileno trên POSIX

#Ba luồng mở sẵn

LuồngSố hiệuMặc định nối vớiChế độ đệm
stdin0Bàn phímTheo dòng
stdout1Màn hìnhTheo dòng khi ra màn hình, đầy mới xả khi chuyển hướng
stderr2Màn hìnhKhông đệm, luôn luôn
Ba luồng này đã mở sẵn trước khi hàm main chạy, và bạn không cần fopen chúng.

#Bộ đệm và ba chế độ

Một byte đi qua hai lớp đệm trước khi nằm trên đĩa. Mỗi lệnh xả chỉ đẩy nó qua được một lớp.
Chế độ đệm quyết định lúc nào thư viện thật sự gọi xuống hệ điều hành.
setvbuf
int setvbuf(FILE *f, char *bo_dem, int che_do, size_t co);

/* che_do: _IOFBF (day moi xa) · _IOLBF (moi dong) · _IONBF (khong dem) */

FILE *f = fopen("nhat-ky.txt", "w");
if (!f) return 1;

setvbuf(f, NULL, _IOLBF, 0);     /* de thu vien tu cap bo dem */

/* Bay gio moi dong nhat ky ra dia ngay khi ghi xong.
   Cham hon, nhung neu chuong trinh sap thi khong mat dong nao. */

#Khi nào dữ liệu biến mất

mat.c
#include <stdio.h>

int main(void) {
    printf("A: nam trong bo dem\n");
    fflush(stdout);                    /* xa tay */

    printf("B: cung nam trong bo dem\n");

    volatile int *p = 0;
    *p = 1;                            /* sap that su */

    return 0;
}
terminal
gcc -std=c11 -o mat mat.c && ./mat > ra.txt
Segmentation fault
cat ra.txt
A: nam trong bo dem

Tự làm thử

  1. Chạy chương trình đo tốc độ ở trên trên máy của bạn và ghi lại hai con số.
  2. Viết chương trình in mười dòng rồi cố ý sập, chạy nó hai lần: một lần ra màn hình, một lần chuyển hướng vào file. So sánh số dòng thu được.
  3. Thêm setvbuf(stdout, NULL, _IONBF, 0) vào chương trình trên và chạy lại, xác nhận không mất dòng nào.
  4. Viết một chương trình in ra stdout và một chương trình in ra stderr, rồi thử ./ct > /dev/null với cả hai.
  5. Cấp một bộ đệm tám byte bằng setvbuf rồi ghi mười hai byte, xác nhận file vẫn đủ mười hai byte.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Luồng là dãy byte, và cùng một bộ hàm dùng được cho file, màn hình, bàn phím và ống dẫn.
  • FILE là kiểu mờ. Chỉ giữ con trỏ, không sao chép, không đọc trường bên trong.
  • stderr không đệm, nên thông báo lỗi phải đi qua nó, cả vì lý do đúng đắn lẫn vì nó không bị mất.
  • Bộ đệm làm chương trình nhanh hơn hàng trăm lần, và làm dữ liệu biến mất khi chương trình sập.
  • fflush đẩy dữ liệu tới hệ điều hành, còn muốn chắc chắn nó nằm trên đĩa thì cần thêm fsync.