This C programming guide is currently only available in Vietnamese. Please toggle the language switch (🇻🇳) in the top navigation to read the full article.

In this first part of the series, we cover setting up the C programming environment on macOS and Linux, installing the GCC/Clang compilers, configuring Visual Studio Code, and running your first "Hello, World!" program via the terminal.

Chào mừng bạn đến với chuỗi bài viết tự học lập trình ngôn ngữ C từ số 0. C là một trong những ngôn ngữ lập trình nền tảng, giúp bạn hiểu sâu sắc cách máy tính quản lý bộ nhớ, tối ưu hóa phần cứng và xây dựng tư duy lập trình vững chắc. Để bắt đầu viết những dòng code C đầu tiên, việc thiết lập một môi trường làm việc chuẩn mực và trực quan là vô cùng cần thiết. Trong bài viết đầu tiên này, chúng ta sẽ cùng thiết lập bộ biên dịch (Compiler) và trình soạn thảo (Editor) trên hệ điều hành macOSLinux.

1. Quy trình biên dịch mã nguồn C hoạt động thế nào?

Khác với các ngôn ngữ thông dịch như JavaScript hay Python (chạy trực tiếp thông qua engine thông dịch), C là một ngôn ngữ biên dịch. Khi bạn viết mã nguồn C (lưu dưới dạng file .c), máy tính không thể hiểu trực tiếp các dòng chữ tiếng Anh đó. Trình biên dịch (Compiler) sẽ chịu trách nhiệm chuyển dịch văn bản của bạn qua nhiều giai đoạn trung gian để tạo ra file thực thi nhị phân mà CPU hiểu được.

Quy trình biên dịch bao gồm 4 giai đoạn chuẩn mực được mô tả chi tiết dưới đây:

[hello.c (Source Code)]
         │
         ▼  (Giai đoạn 1: Preprocessor - Tiền xử lý)
[hello.i (Preprocessed Code)]
         │
         ▼  (Giai đoạn 2: Compiler - Biên dịch sang mã Assembly)
[hello.s (Assembly Code)]
         │
         ▼  (Giai đoạn 3: Assembler - Lắp ráp sang mã máy thô)
[hello.o (Object File)] ─────────┐
                                 ▼ (Giai đoạn 4: Linker - Liên kết thư viện)
                          [hello (Executable Binary)]
              

Giai đoạn 1: Tiền xử lý (Preprocessing)

Bộ tiền xử lý (Preprocessor) quét mã nguồn và thực thi các chỉ thị bắt đầu bằng dấu # (như #include, #define, #ifdef). Các hằng số định nghĩa qua macro sẽ được chèn trực tiếp, các tệp tiêu đề (header files) sẽ được sao chép hoàn toàn nội dung vào file. Bạn có thể xuất kết quả tiền xử lý bằng lệnh:

gcc -E hello.c -o hello.i

Nếu mở file hello.i vừa tạo ra, bạn sẽ thấy nó dài hàng trăm dòng vì chứa toàn bộ mã khai báo hàm của tệp tiêu đề stdio.h được chèn vào đầu.

Giai đoạn 2: Biên dịch (Compilation)

Trình biên dịch nhận đầu vào là mã nguồn đã được tiền xử lý (`hello.i`) và phân tích cú pháp, chuyển đổi các cấu trúc lệnh của ngôn ngữ C sang mã Assembly (hợp ngữ) của cấu trúc chip CPU tương ứng (như x86_64 hoặc ARM). Để xuất file mã Assembly, hãy chạy lệnh:

gcc -S hello.i -o hello.s

Khi mở file hello.s, bạn sẽ thấy các lệnh hợp ngữ cơ bản như pushq, movq, call, ret...

Giai đoạn 3: Lắp ráp (Assembly)

Bộ lắp ráp (Assembler) chuyển đổi mã Assembly văn bản (`hello.s`) thành mã máy nhị phân thô (Machine Code). Kết quả là một file đối tượng (Object file). File này chứa mã máy của CPU nhưng chưa thể chạy độc lập vì chưa có địa chỉ liên kết các hàm thư viện ngoài. Xuất file Object bằng lệnh:

gcc -c hello.s -o hello.o

Giai đoạn 4: Liên kết (Linking)

Bộ liên kết (Linker) sẽ ghép nối file đối tượng (`hello.o`) của bạn với mã nhị phân của các thư viện hệ thống đã được biên dịch sẵn (như hàm printf nằm trong thư viện C chuẩn `libc.a` hoặc `libc.so`). Linker giải quyết các địa chỉ ô nhớ và gộp tất cả thành file nhị phân thực thi cuối cùng:

gcc hello.o -o hello

2. Cài đặt Trình biên dịch (Compiler)

Trên hệ điều hành macOS

Trình biên dịch mặc định trên macOS là Clang (được bọc dưới lệnh gcc để tương thích). Để cài đặt nhanh gọn nhất, bạn không cần tải toàn bộ bộ Xcode nặng hàng chục GB, chỉ cần cài đặt gói công cụ dòng lệnh (Command Line Tools):

  1. Mở ứng dụng Terminal (nhấn Cmd + Space, gõ "Terminal" và nhấn Enter).
  2. Nhập lệnh sau và nhấn Enter:
Terminal
xcode-select --install

Một hộp thoại xác nhận sẽ hiện lên yêu cầu bạn đồng ý cài đặt. Hãy bấm Install và đợi hệ thống tải xuống hoàn tất.

Trên hệ điều hành Linux

Hầu hết các bản phân phối Linux sử dụng bộ trình biên dịch GCC (GNU Compiler Collection) cực kỳ mạnh mẽ. Để cài đặt bộ công cụ phát triển cơ bản (bao gồm GCC, Make và các thư viện hệ thống cần thiết):

Đối với Ubuntu/Debian/Mint, mở Terminal và chạy lệnh:

Terminal (Ubuntu/Debian)
sudo apt update
sudo apt install build-essential

Đối với Fedora/CentOS/RHEL:

Terminal (Fedora)
sudo dnf groupinstall "Development Tools"

Kiểm tra cài đặt compiler thành công

Để chắc chắn máy tính của bạn đã có compiler, gõ lệnh kiểm tra phiên bản sau trong Terminal:

Terminal
gcc --version

Nếu màn hình hiển thị thông tin phiên bản của gcc hoặc Apple clang, xin chúc mừng, bạn đã sẵn sàng!

3. Cài đặt Trình soạn thảo mã nguồn (VS Code)

Chúng ta sẽ sử dụng Visual Studio Code (VS Code) - một code editor nhẹ, miễn phí, và cực kỳ phổ biến của Microsoft.

  1. Truy cập trang chủ code.visualstudio.com để tải về file cài đặt phù hợp với macOS hoặc Linux của bạn.
  2. Cài đặt ứng dụng và mở VS Code lên.
  3. Cài đặt Tiện ích mở rộng (Extension) hỗ trợ ngôn ngữ C:
    • Nhấn vào biểu tượng Extensions ở thanh lề trái (hoặc bấm tổ hợp phím Cmd + Shift + X).
    • Tìm kiếm từ khóa: C/C++ Extension Pack (của Microsoft).
    • Bấm nút Install để cài đặt. Tiện ích này sẽ mang lại khả năng gợi ý mã nguồn (IntelliSense), định dạng code tự động và gỡ lỗi (debugging).

4. Viết và chạy chương trình C đầu tiên

Giờ là lúc viết chương trình huyền thoại: hiển thị dòng chữ "Hello, World!".

  1. Mở một thư mục trống bằng VS Code (File -> Open Folder).
  2. Tạo một file mới tên là hello.c.
  3. Nhập đoạn mã nguồn chuẩn mực sau vào file:
hello.c
#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

Lưu file lại (Cmd + S).

Biên dịch và chạy qua Terminal

Mở Terminal tích hợp ngay trong VS Code bằng phím tắt Ctrl + ` (dấu huyền) hoặc chọn Terminal -> New Terminal trên thanh công cụ.

1. Tiến hành biên dịch file hello.c thành file thực thi tên là hello:

Terminal
gcc hello.c -o hello

2. Chạy chương trình vừa được biên dịch xong:

Terminal
./hello

Bạn sẽ thấy dòng chữ Hello, World! hiển thị ngay trên Terminal của mình. Bạn đã chính thức bước chân vào thế giới lập trình ngôn ngữ C!

📥 Tải về mã nguồn mẫu: hello.c

5. Khắc phục các lỗi biên dịch thường gặp

  • Lỗi: command not found: gcc: Hệ điều hành chưa nhận dạng được bộ compiler. Hãy đảm bảo bạn đã cài đặt thành công Xcode Command Line Tools (trên macOS) hoặc gói build-essential (trên Linux).
  • Lỗi: implicit declaration of function 'printf': Lỗi này xảy ra khi bạn quên chỉ thị #include <stdio.h> ở đầu file. Compiler sẽ không nhận biết được hàm printf đến từ đâu.

Thử thách nhỏ dành cho bạn:

Hãy thử chỉnh sửa văn bản bên trong hàm printf("...") thành tên của bạn hoặc bất cứ nội dung gì bạn thích, lưu lại, biên dịch và chạy lại chương trình xem kết quả nhé!

6. Cờ biên dịch quan trọng (Compiler Flags)

Khi bạn chạy lệnh đơn giản gcc hello.c -o hello, trình biên dịch sẽ biên dịch thành công mà hầu như không đưa ra bất kỳ cảnh báo nào. Điều này không có nghĩa là mã nguồn của bạn hoàn hảo — GCC mặc định ẩn rất nhiều cảnh báo hữu ích. Trong thực tế chuyên nghiệp, lập trình viên luôn bật các cờ (flags) để buộc compiler phân tích mã nguồn nghiêm ngặt hơn, giúp phát hiện lỗi tiềm ẩn ngay từ giai đoạn biên dịch thay vì khi chương trình đang chạy.

Bảng dưới đây liệt kê các cờ biên dịch quan trọng nhất mà bạn nên nắm vững:

Cờ (Flag) Mô tả chức năng
-Wall Bật hầu hết các cảnh báo phổ biến (biến không sử dụng, thiếu return, so sánh sai kiểu...)
-Wextra Bật thêm các cảnh báo bổ sung mà -Wall chưa bao gồm
-Werror Biến mọi cảnh báo thành lỗi — compiler sẽ dừng biên dịch nếu có warning
-g Nhúng thông tin debug vào file thực thi, phục vụ cho GDB/LLDB và Valgrind
-O0 Không tối ưu hóa (mặc định) — dễ debug nhất vì mã máy ánh xạ trực tiếp với mã nguồn
-O1, -O2 Tối ưu hóa trung bình đến cao — tăng tốc chương trình, -O2 là mức phổ biến cho bản phát hành
-O3 Tối ưu hóa tích cực nhất — có thể tăng kích thước file và đôi khi gây lỗi với mã không chuẩn
-std=c11 / -std=c17 Chỉ định phiên bản tiêu chuẩn C sử dụng (C11 hoặc C17)
-pedantic Tuân thủ nghiêm ngặt tiêu chuẩn ISO C, cảnh báo mọi phần mở rộng không chuẩn

Lệnh biên dịch khuyến nghị khi phát triển (development):

Terminal
gcc -Wall -Wextra -g -std=c17 hello.c -o hello

Lệnh biên dịch khuyến nghị khi phát hành (release):

Terminal
gcc -Wall -Wextra -Werror -O2 -std=c17 hello.c -o hello

7. Tự động hóa biên dịch với Makefile

Khi dự án phát triển lớn hơn với nhiều file mã nguồn, việc gõ lại các lệnh gcc dài dòng mỗi lần biên dịch trở nên cực kỳ bất tiện và dễ sai sót. Makefile là một công cụ tự động hóa kinh điển giải quyết vấn đề này. Nó cho phép bạn định nghĩa toàn bộ quy trình biên dịch một lần duy nhất, sau đó chỉ cần gõ lệnh make để thực thi. Makefile còn hỗ trợ biên dịch gia tăng (incremental build) — chỉ biên dịch lại những file đã thay đổi, tiết kiệm đáng kể thời gian.

Dưới đây là một Makefile mẫu hoàn chỉnh cho dự án C cơ bản:

Makefile
# Biến cấu hình
CC      = gcc
CFLAGS  = -Wall -Wextra -g -std=c17
TARGET  = hello
SRCS    = hello.c
OBJS    = $(SRCS:.c=.o)

# Quy tắc mặc định: biên dịch toàn bộ dự án
all: $(TARGET)

# Liên kết các file object thành file thực thi
$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

# Biên dịch từng file .c thành file .o
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Dọn dẹp file biên dịch
clean:
	rm -f $(OBJS) $(TARGET)

# Đánh dấu target không phải là file
.PHONY: all clean

Cách sử dụng Makefile rất đơn giản. Mở Terminal tại thư mục chứa Makefile và chạy:

Terminal
# Biên dịch dự án
make

# Chạy chương trình
./hello

# Xóa file biên dịch để build lại từ đầu
make clean

Lưu ý: Đối với các dự án lớn hơn với hàng chục hoặc hàng trăm file mã nguồn, bạn nên tìm hiểu CMake — một hệ thống build đa nền tảng mạnh mẽ hơn, có khả năng tự động sinh ra Makefile phù hợp với từng hệ điều hành.

8. Gỡ lỗi cơ bản với GDB (GNU Debugger)

Khi chương trình chạy sai kết quả hoặc bị crash, việc đặt printf khắp nơi để kiểm tra giá trị biến là cách làm thủ công và kém hiệu quả. GDB (GNU Debugger) là công cụ gỡ lỗi chuyên nghiệp cho phép bạn dừng chương trình tại bất kỳ dòng nào, kiểm tra giá trị biến, và đi từng bước qua từng lệnh để tìm chính xác vị trí lỗi.

GDB đã được cài đặt sẵn khi bạn cài build-essential (Linux) hoặc Xcode Command Line Tools (macOS). Trên macOS, bạn cũng có thể sử dụng LLDB (lldb ./program) — trình gỡ lỗi mặc định của Apple với giao diện lệnh tương tự.

Bước 1: Biên dịch chương trình với cờ -g (nhúng thông tin debug) và -O0 (tắt tối ưu hóa):

Terminal
gcc -g -O0 hello.c -o hello

Bước 2: Khởi chạy GDB với file thực thi:

Terminal
gdb ./hello

Bước 3: Sử dụng các lệnh GDB cơ bản. Bảng dưới đây tổng hợp các lệnh thiết yếu nhất:

Lệnh GDB Chức năng
run Chạy chương trình từ đầu
break main Đặt điểm dừng (breakpoint) tại hàm main
break 15 Đặt breakpoint tại dòng số 15
next Thực thi dòng tiếp theo (bỏ qua nội dung bên trong hàm con)
step Thực thi dòng tiếp theo (đi vào bên trong hàm con nếu có)
print x In giá trị hiện tại của biến x
backtrace Hiển thị ngăn xếp lời gọi hàm (call stack) — hữu ích khi chương trình crash
continue Tiếp tục chạy cho đến breakpoint tiếp theo hoặc kết thúc
quit Thoát khỏi GDB

Ví dụ quy trình gỡ lỗi cơ bản:

Phiên GDB mẫu
$ gdb ./hello
(gdb) break main        # Đặt breakpoint tại hàm main
(gdb) run               # Chạy chương trình — dừng tại main
(gdb) next              # Thực thi dòng tiếp theo
(gdb) print x           # Kiểm tra giá trị biến x
(gdb) continue          # Tiếp tục chạy đến hết
(gdb) quit              # Thoát GDB
📝 Kiểm tra kiến thức bài 1
Lệnh nào sau đây dùng để biên dịch file hello.c thành file thực thi mã máy có tên là hello trên Terminal?

Related Articles

Bài viết liên quan trong series

Lesson 2: C Syntax, Variables, Data Types & I/O Bài 2: Cú pháp C cơ bản, Biến, Kiểu dữ liệu & Nhập xuất Back to C Series Overview Quay lại Lộ trình C Series