Menu

Thư viện chuẩn C: bản đồ các header đáng biết

Mỗi header chuẩn cung cấp cho bạn những gì: stdio.h, stdlib.h, string.h, math.h, time.h, ctype.h, limits.h, stdbool.h, stdint.h và assert.h, mỗi header kèm một ví dụ ngắn chạy được.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Bản thân ngôn ngữ C rất nhỏ: kiểu dữ liệu, toán tử, luồng điều khiển, hàm, con trỏ. Nó không thể in ra màn hình, đọc tệp, so sánh chuỗi hay cấp phát bộ nhớ. Tất cả những việc đó thuộc về thư viện chuẩn (standard library): một bộ header mà mọi bản cài đặt đúng chuẩn đều đi kèm.

Trang này là một tấm bản đồ. Mỗi phần cho biết một header dùng để làm gì, đưa ra một ví dụ ngắn chạy được, và trỏ tới trang chi tiết hơn nếu có.

stdio.h: nhập và xuất

Header bạn include đầu tiên trong gần như mọi chương trình. Nó chứa printf và scanf, kiểu FILE và các hàm làm việc với tệp, cùng các hàm nhập theo dòng an toàn hơn.

Những hàm thiết yếu: printf, fprintf, sprintf, snprintf cho đầu ra; scanf, fscanf, sscanf, fgets, getchar cho đầu vào; fopen, fclose, fread, fwrite, fseek cho tệp. Nội dung chi tiết hơn nằm ở printf, scanf và xử lý tệp.

Một quy tắc nên nhớ: đừng bao giờ dùng gets(). Hàm này không có cách nào biết kích thước bộ đệm của bạn và đã bị loại khỏi tiêu chuẩn từ C11. fgets(buf, sizeof buf, stdin) là hàm thay thế.

stdlib.h: các tiện ích chung

Header gom đủ thứ: bộ nhớ, chuyển đổi, số ngẫu nhiên, điều khiển tiến trình, sắp xếp.

Nổi bật: malloc, calloc, realloc, free (xem cấp phát bộ nhớ động); atoi, atof, strtol, strtod để chuyển đổi, và nên ưu tiên họ strto* vì chúng báo được lỗi, trong khi atoi âm thầm trả về 0; rand và srand (xem số ngẫu nhiên); exit, abort, getenv, system; và qsort/bsearch.

string.h: chuỗi và khối bộ nhớ

Chuỗi trong C là mảng char kết thúc bằng '\0', và mọi thao tác trên chúng đều là một lời gọi hàm.

Hai thói quen giúp header này không trở thành nguồn gây tràn bộ đệm: luôn biết kích thước của vùng đích, và nhớ rằng strcmp trả về 0 khi bằng nhau, nên if (strcmp(a, b)) đúng khi hai chuỗi khác nhau. Các hàm xử lý chuỗi trình bày chi tiết các cách dùng strncpy/snprintf an toàn hơn.

math.h: toán học

sqrt, pow, fabs, floor, ceil, round, fmod, nhóm hàm lượng giác và logarit, INFINITY và NAN.

Đây là header duy nhất có thể cần thêm cờ cho trình liên kết: trên Linux, hãy thêm -lm ở cuối lệnh biên dịch, nếu không trình liên kết sẽ báo "undefined reference to sqrt". Xem đầy đủ ở các hàm toán học.

time.h: đồng hồ và ngày tháng

Hai độ lệch trong struct tm khiến ai cũng vấp: tm_year đếm từ năm 1900, và tm_mon bắt đầu từ 0. time(NULL) cũng là seed thường dùng cho srand.

ctype.h: phân loại ký tự

Nhỏ, nhưng hữu ích hơn vẻ ngoài. Mỗi hàm nhận một ký tự và trả lời một câu hỏi có hoặc không, hoặc đổi chữ hoa chữ thường.

Bộ đầy đủ: isalpha, isdigit, isalnum, isspace, isupper, islower, ispunct, isxdigit, isprint, cùng toupper và tolower.

Hãy để ý phép ép kiểu (unsigned char). Các hàm này được định nghĩa cho những giá trị biểu diễn được bằng unsigned char cộng thêm EOF; trên đa số hệ thống, char thường là kiểu có dấu, nên một byte lớn hơn 127 sẽ đến dưới dạng số âm và lời gọi trở thành undefined behavior. Phép ép kiểu không tốn gì và loại bỏ cả một lớp vấn đề.

Và hãy ưu tiên các hàm này thay vì tự viết phép kiểm tra. c >= '0' && c <= '9' tình cờ chạy đúng, nhưng isdigit(c) nói đúng ý bạn và luôn đúng ở mọi nơi.

limits.h và float.h: giới hạn của các kiểu

Một int có thể lớn tới đâu trên máy này? Các header này trả lời câu hỏi đó, và chính những câu trả lời ấy là lý do code khả chuyển phải hỏi thay vì giả định.

INT_MAX là cách kiểm tra tràn số trước khi nó xảy ra, điều này quan trọng vì tràn số có dấu là undefined behavior, chứ không phải một vòng quay lại mà bạn có thể kiểm tra sau đó.

stdbool.h và stdint.h: kiểu dữ liệu tốt hơn

C không có kiểu boolean cho đến C99. <stdbool.h> cung cấp một kiểu như vậy:

bool, true và false là các macro cho _Bool, 1 và 0, nhưng chúng làm ý định trở nên rõ ràng. (Từ C23 chúng trở thành từ khóa và không cần header nữa.)

<stdint.h> cung cấp các kiểu có độ rộng được đảm bảo, thiết yếu cho định dạng tệp, giao thức mạng và mọi thứ chạy trên hệ nhúng, nơi "một int chắc là 32 bit" là không đủ tốt:

Các macro kiểu PRId32 từ <inttypes.h> mở rộng thành đặc tả printf đúng cho từng kiểu có độ rộng cố định trên nền tảng hiện tại. Đó là cách khả chuyển để in chúng, vì %d đúng với int32_t trên một số hệ thống và sai trên những hệ thống khác.

assert.h: kiểm tra các giả định

assert(expr) không làm gì khi biểu thức đúng và dừng chương trình kèm thông báo khi biểu thức sai.

Biên dịch với -DNDEBUG sẽ loại bỏ mọi assertion, nên chúng không tốn gì trong bản build release. Đó cũng là lời cảnh báo: đừng bao giờ đặt tác dụng phụ bên trong assert. assert(read_next() > 0) sẽ khiến việc đọc dừng hẳn trong bản build release, và bug đó chỉ xuất hiện trên môi trường production.

Assertion dành cho lỗi của bạn: những điều kiện lẽ ra không thể xảy ra nếu code đúng. Dữ liệu người dùng nhập sai không phải là assertion; đó là một câu lệnh if và một thông báo lỗi.

Phần còn lại, nói ngắn gọn

  • <stddef.h>: size_t, ptrdiff_t, NULL, offsetof
  • <stdarg.h>: hàm có số đối số thay đổi, cơ chế đứng sau printf
  • <errno.h>: errno cùng perror và strerror để báo vì sao một lời gọi thất bại
  • <signal.h>: xử lý tín hiệu như Ctrl-C
  • <setjmp.h>: nhảy không cục bộ; hiếm khi là lựa chọn đúng
  • <locale.h>, <wchar.h>, <wctype.h>: locale và ký tự rộng
  • <stdnoreturn.h>, <stdalign.h>, <threads.h>, <stdatomic.h>: những bổ sung của C11

Hai thói quen giúp dùng thư viện dễ chịu hơn. Đọc trang hướng dẫn (man page) trước khi đoán chữ ký hàm: man 3 strtol trên macOS và Linux là nguồn chính xác và nhanh. Và kiểm tra giá trị trả về: malloc, fopen và strtol đều báo lỗi khi thất bại, và một chương trình bỏ qua chúng sẽ hỏng muộn hơn nhiều và khó hiểu hơn nhiều.

Câu hỏi thường gặp

Thư viện chuẩn C là gì?

Là tập hợp các hàm, kiểu dữ liệu và macro mà mọi bản cài đặt C đúng chuẩn đều cung cấp, chia thành khoảng 30 header. Nó bao gồm nhập và xuất (<stdio.h>), bộ nhớ và chuyển đổi (<stdlib.h>), chuỗi (<string.h>), toán học (<math.h>), thời gian, phân loại ký tự, và giới hạn của các kiểu số. C không có sẵn nhập xuất hay xử lý chuỗi: tất cả đều đến từ đây.

stdio.h và stdlib.h khác nhau thế nào?

<stdio.h> là nhập và xuất: printf, scanf, fopen, fgets, FILE. <stdlib.h> là các tiện ích chung: malloc và free, atoi và strtol, rand và srand, exit, qsort, bsearch. Một chương trình thông thường include cả hai.

ctype.h dùng để làm gì?

Nó phân loại và chuyển đổi từng ký tự: isdigit, isalpha, isalnum, isspace, isupper, islower, ispunct, cùng toupper và tolower. Dùng chúng thay vì tự viết phép so sánh như c >= '0' && c <= '9' vừa rõ ràng hơn vừa đúng với mọi locale và bảng mã ký tự.

Có cần liên kết gì thêm để dùng thư viện chuẩn C không?

Không, trừ một ngoại lệ: các hàm toán học. Thư viện C chuẩn được liên kết tự động, nhưng trên Linux phần cài đặt của <math.h> nằm trong một thư viện riêng, nên sqrt và các hàm cùng nhóm cần -lm ở cuối lệnh biên dịch. Mọi thứ khác chỉ cần đúng dòng #include.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu