C là một ngôn ngữ lập trình biên dịch, đa dụng, do Dennis Ritchie tạo ra tại Bell Labs năm 1972 để viết hệ điều hành Unix. C đã tồn tại lâu hơn gần như mọi thứ cùng thời, và lý do rất đơn giản: C chỉ nằm trên phần cứng đúng một lớp mỏng. C cho bạn truy cập trực tiếp vào bộ nhớ và vào những khả năng thật sự của CPU, trong khi code vẫn dễ đọc như do con người viết.
Nửa thế kỷ sau, C vẫn là ngôn ngữ dùng để xây dựng các ngôn ngữ khác.
Một chương trình C hoàn chỉnh
Đây là hình hài của cả ngôn ngữ trong sáu dòng. Nhấn Chạy.
#include <stdio.h> nạp các khai báo nhập xuất chuẩn để chương trình biết printf là gì. main là nơi mọi chương trình C bắt đầu. printf in ra văn bản, \n là ký tự xuống dòng, và return 0 báo cho hệ điều hành biết chương trình đã kết thúc thành công. Đó là một chương trình hoàn chỉnh, biên dịch được: không cần lớp (class) nào bao quanh và không có framework nào bên dưới.
Điều gì làm C khác biệt
Phần lớn ngôn ngữ hiện đại che giấu phần cứng khỏi bạn. C cố ý không làm vậy.
- C được biên dịch ra mã máy. Một chương trình C trở thành các lệnh máy cho một loại CPU và chạy trực tiếp, không có trình thông dịch hay máy ảo nào ở giữa.
- Bạn tự quản lý bộ nhớ. Không có garbage collector. Bạn xin bộ nhớ và tự trả lại. Như vậy tốn công hơn, nhưng đó cũng là lý do chương trình C dùng rất ít bộ nhớ và có thời gian chạy dễ dự đoán.
- C rất nhỏ gọn. C có khoảng 32 từ khóa. Bạn có thể nhớ trọn cả ngôn ngữ trong đầu, điều không thể làm với C++, Java hay Python.
- Gần như không có runtime. Một chương trình C có thể chạy trên vi điều khiển chỉ có vài kilobyte RAM và hoàn toàn không có hệ điều hành.
Cái giá phải trả là C chọn sự minh bạch thay vì sự an toàn. C cho phép bạn đọc vượt quá cuối mảng hay dùng vùng nhớ đã giải phóng, và không ngăn bạn lại. Những lỗi đó đều có tên: segmentation fault, rò rỉ bộ nhớ, undefined behavior. Học C phần lớn là học cách tránh chúng.
C thực sự chạy ở đâu
C không phải là di tích lịch sử. Nó đang chạy trên chính thiết bị bạn dùng để đọc trang này, và ở nhiều chỗ khác nhau.
- Hệ điều hành. Nhân Linux có khoảng 30 triệu dòng C. Nhiều phần quan trọng của Windows và nhân hệ điều hành của Apple cũng được viết bằng C.
- Hệ thống nhúng. Vi điều khiển trong ô tô, máy điều nhiệt, thiết bị y tế, router và máy giặt hầu hết được lập trình bằng C. Khi cả con chip chỉ có 32 KB bộ nhớ, C thường là lựa chọn thực tế duy nhất.
- Cơ sở dữ liệu. SQLite, PostgreSQL, MySQL và Redis đều là chương trình C.
- Runtime của ngôn ngữ. CPython, trình thông dịch Python chuẩn, được viết bằng C. MRI của Ruby, Zend engine của PHP và phần lõi của Lua cũng vậy. Khi Python "quá chậm", cách khắc phục thường là viết một C extension.
- Công cụ và thư viện. Git, curl, FFmpeg, OpenSSL, zlib, và lại SQLite: tầng hạ tầng của ngành điện toán là C.
Đây cũng là lý do giao diện nhị phân ứng dụng (application binary interface) của C quan trọng: gần như mọi ngôn ngữ đều gọi được hàm C. Python, Rust, Go, Java và JavaScript đều có cách gọi vào thư viện C. C là ngôn ngữ chung giữa các ngôn ngữ.
Vì sao nên học C
Có hai câu trả lời thật lòng, và chúng dành cho hai kiểu người khác nhau.
Nếu bạn muốn làm phần mềm hệ thống, như nhân hệ điều hành, driver, firmware nhúng, cơ sở dữ liệu, thì C là ngôn ngữ làm việc của lĩnh vực này, và phần lớn tin tuyển dụng ở mảng đó ghi rõ yêu cầu C (thường kèm C++ hoặc Rust). Rất khó đóng góp cho Linux, PostgreSQL hay một codebase vi điều khiển nếu bạn không đọc C thành thạo.
Nếu bạn muốn giỏi hơn ở ngôn ngữ mình đang dùng, C dạy bạn những gì nằm bên dưới. Sau khi học C, bạn hiểu vì sao chỉ số mảng bắt đầu từ 0, chuỗi thực chất là gì, vì sao truyền một đối tượng lớn theo giá trị lại tốn kém, stack và heap là gì, và con trỏ null thực sự trỏ vào đâu. Những khái niệm đó xuất hiện trong mọi ngôn ngữ; C là nơi chúng hiện ra rõ ràng thay vì bị che giấu.
Chương trình đó chỉ có ba dòng C, nhưng cũng là một minh họa trọn vẹn về biến: một chiếc hộp có tên nằm tại một địa chỉ. Ở phần lớn ngôn ngữ khác, bạn thậm chí không thể đặt câu hỏi đó.
C và C++
C++ bắt đầu năm 1979 với tên gọi "C với lớp" và phát triển thành một trong những ngôn ngữ lớn nhất hiện có: lớp, template, ngoại lệ, nạp chồng toán tử, STL, smart pointer, và một thư viện chuẩn đồ sộ hơn hẳn toàn bộ đặc tả của C.
Trên thực tế, quan hệ giữa hai ngôn ngữ là:
- C++ giữ lại cú pháp của C, nên
if,for,while, toán tử, con trỏ và mảng trông giống nhau ở cả hai. - Phần lớn code C viết tốt biên dịch được dưới dạng C++, với vài ngoại lệ (chẳng hạn C++ bắt buộc ép kiểu kết quả của
malloc). - Code C++ gần như không bao giờ là code C hợp lệ.
Nếu bạn định học cả hai, học C trước là một lợi thế thật sự: C++ xây dựng trực tiếp trên mô hình bộ nhớ của C, và những phần khiến người học C++ bối rối nhất, như con trỏ, tham chiếu, ngữ nghĩa sao chép, hàm hủy, đều là lời giải cho những vấn đề mà C cho bạn thấy rõ ràng.
Tuy vậy, C không phải là tập con chính thức của C++. Hai ngôn ngữ có các chuẩn riêng phát triển độc lập, và C hiện đại có những tính năng C++ không có (designated initializer có từ C99, con trỏ restrict và mảng có độ dài thay đổi).
C nào: các phiên bản chuẩn
C được chuẩn hóa, và phiên bản rất quan trọng khi bạn đọc code cũ.
| Chuẩn | Năm | Điểm đáng chú ý |
|---|---|---|
| C89 / C90 | 1989 | Mốc cơ sở lâu đời. Chỉ được khai báo biến ở đầu khối lệnh. |
| C99 | 1999 | Chú thích //, khai báo biến ở bất kỳ đâu, bool qua stdbool.h, long long |
| C11 | 2011 | Luồng (thread), atomic, các hàm thư viện an toàn hơn |
| C17 | 2018 | Bản sửa lỗi của C11, là mặc định phổ biến hiện nay |
| C23 | 2024 | true/false/bool thành từ khóa, nullptr, số nhị phân dạng literal |
Tài liệu này hướng tới C17 và ghi chú các tính năng từ C99 trở lên khi cần. Trình biên dịch mặc định dùng một chuẩn khá mới, nhưng bạn có thể chỉ định rõ:
gcc -std=c17 -Wall -Wextra hello.c -o hello
Những gì C không có
Hiểu C cũng là biết C thiếu những gì, vì gần như mọi câu hỏi "làm sao để..." đều có câu trả lời khác hẳn so với ngôn ngữ hiện đại.
- Không có kiểu chuỗi. Chuỗi trong C là một mảng
charkết thúc bằng byte 0. Nối hai chuỗi nghĩa là gọi một hàm và lo xem vùng đích có đủ lớn không. - Không có collection. Không có list, map hay set. Bạn chỉ có mảng cố định và con trỏ; bảng băm là thứ bạn tự viết hoặc liên kết từ một thư viện.
- Không có ngoại lệ. Hàm báo lỗi bằng cách trả về một giá trị đặc biệt, như
-1,NULLhoặc một mã trạng thái, và người gọi phải tự kiểm tra. - Không có generic hay nạp chồng hàm. Một tên hàm là một hàm. Vì thế thư viện chuẩn có
abs,labsvàfabscho ba kiểu khác nhau. - Không có namespace hay module. Mọi hàm không
staticdùng chung một không gian tên toàn cục, nên các thư viện C gắn tiền tố cho mọi thứ (sqlite3_open,png_read_info). - Không có garbage collector. Mỗi lần
malloccần một lầnfreetương ứng, và quên là gây ra rò rỉ bộ nhớ.
Không điều nào trong số đó là sơ suất. Một số là cơ chế runtime mà C từ chối mang theo (garbage collector, cơ chế xử lý ngoại lệ, chuỗi tự giãn), và đó là phần lớn lý do C vừa vặn trên một con chip chỉ có 2 KB RAM; số khác, như namespace và generic, là tính năng ngôn ngữ ra đời sau khi C được thiết kế và bị để ngoài nhằm giữ ngôn ngữ nhỏ gọn.
Vùng đệm có 32 byte vì bạn quyết định như vậy là đủ. C sẽ không kiểm tra, và quyết định đó chính là toàn bộ tính cách của ngôn ngữ này gói trong một dòng.
C có khó không?
C nhỏ gọn nhưng không khoan nhượng. Không có nhiều cú pháp phải nhớ: bạn có thể đọc hết cả ngôn ngữ trong một tuần. Điều tốn thời gian là tính kỷ luật: theo dõi xem ai sở hữu vùng nhớ nào, nhớ rằng chuỗi chỉ là các byte kết thúc bằng số 0, kiểm tra con trỏ không phải null trước khi dùng nó.
Phần lớn độ khó của C không nằm ở khái niệm mà ở chỗ thiếu rào chắn an toàn: trình biên dịch sẽ không cứu bạn khỏi một lần ghi vượt giới hạn hay một con trỏ treo, nên bạn học cách làm việc chính xác. (Bản thân con trỏ cũng cần nỗ lực thật sự trong lần đầu, điều đó hoàn toàn bình thường, và chương con trỏ đi qua chúng thật chậm rãi.) Sự chính xác đó là kỹ năng bạn mang theo sang mọi ngôn ngữ sau này.
Câu hỏi thường gặp
Ngôn ngữ lập trình C là gì?
C là một ngôn ngữ lập trình biên dịch, đa dụng, do Dennis Ritchie tạo ra tại Bell Labs năm 1972. C cho bạn quyền kiểm soát trực tiếp bộ nhớ và bám rất sát những gì CPU thực sự làm, vì vậy hệ điều hành, driver thiết bị và cơ sở dữ liệu đến nay vẫn được viết bằng C.
Ngày nay C được dùng để làm gì?
Nhân hệ điều hành (Linux, một phần Windows và macOS), firmware nhúng trong ô tô, đồ gia dụng và vi điều khiển, cơ sở dữ liệu như SQLite và PostgreSQL, runtime và phần mềm như CPython và Redis, cùng các thư viện đòi hỏi hiệu năng cao mà những ngôn ngữ khác gọi tới.
Năm 2026 có còn nên học C không?
Có, vì hai lý do. C vẫn là ngôn ngữ của lập trình hệ thống và lập trình nhúng, một lĩnh vực lớn với mức lương tốt. Và C dạy bạn bộ nhớ, con trỏ và stack thực sự hoạt động ra sao, kiến thức giúp bạn giỏi hơn ở mọi ngôn ngữ khác, vì phần lớn chúng được cài đặt bằng C.
C và C++ khác nhau thế nào?
C++ khởi đầu là "C với lớp", giữ lại phần lớn cú pháp của C và bổ sung lập trình hướng đối tượng, template, thư viện chuẩn và nhiều thứ khác. C nhỏ hơn nhiều: không có lớp, không có ngoại lệ, không có generic. Phần lớn code C hợp lệ biên dịch được dưới dạng C++, nhưng chiều ngược lại thì không.