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

Bốn giai đoạn và cách xem từng cái

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

  • Kể bốn giai đoạn và tệp ra của từng giai đoạn
  • Dùng -E, -S, -c, -save-temps để xem file trung gian
  • Dùng -v, -###, -H để hiểu gcc gọi gì
  • In mọi macro tiền định nghĩa bằng -dM

Một lệnh gcc main.c -o mainthật ra chạy bốn chương trình nối tiếp nhau. Hiểu bốn giai đoạn đó cho bạn công cụ để trả lời "vì sao nó không dịch được" ở đúng bước gây lỗi, thay vì đoán mò.

#Bốn giai đoạn

  1. Tiền xử lý (cpp)

    Xử lý mọi dòng bắt đầu bằng #: dán nội dung #include, thay #define, chọn nhánh #if. Ra: một tệp .i chỉ còn C thuần, không còn chỉ thị tiền xử lý.
  2. Biên dịch (cc1)

    Dịch C thành assembly cho kiến trúc đích. Đây là bước làm phần lớn công việc: phân tích cú pháp, kiểm tra kiểu, tối ưu. Ra: một tệp .s.
  3. Hợp dịch (as)

    Dịch assembly thành mã máy, đóng vào tệp đối tượng. Ra: một tệp .o, có mã máy nhưng địa chỉ các hàm ngoài còn để trống.
  4. Liên kết (ld)

    Ghép các tệp .o và thư viện lại, điền các địa chỉ còn trống, tạo tệp thực thi. Ra: chương trình chạy được.
  main.c
    |  cpp   (tien xu ly)      -E dung o day
  main.i
    |  cc1   (bien dich)       -S dung o day
  main.s
    |  as    (hop dich)        -c dung o day
  main.o
    |  ld    (lien ket)        (mac dinh chay het)
  main.exe

#Xem từng bước

vd.c
#include <stdio.h>

#define BINH_PHUONG(x) ((x) * (x))

int tong_binh_phuong(int n) {
    int s = 0;
    for (int i = 1; i <= n; ++i) s += BINH_PHUONG(i);
    return s;
}

int main(void) {
    printf("%d\n", tong_binh_phuong(5));
    return 0;
}
terminal
# -E: xem macro DA duoc thay chua
gcc -std=c11 -E vd.c | grep -A3 'tong_binh_phuong(int'
int tong_binh_phuong(int n) {
    int s = 0;
    for (int i = 1; i <= n; ++i) s += ((i) * (i));
    return s;
terminal
# -S: xem assembly. Dem so lenh nhan va cong trong vong lap
gcc -std=c11 -O0 -S vd.c -o vd.s
grep -c 'imull\|addl' vd.s
3

#save-temps

terminal
# Giu TAT CA file trung gian trong mot lan dich
gcc -std=c11 -save-temps -c vd.c -o vd.o
ls vd.i vd.s vd.o
vd.i  vd.o  vd.s

#gcc gọi gì

terminal
# -v: in moi chuong trinh gcc goi, kem duong dan day du
gcc -std=c11 vd.c -o vd.exe -v 2>&1 | grep -E 'cc1|GNU C'
 .../libexec/gcc/x86_64-w64-mingw32/8.1.0/cc1.exe
GNU C11 (...MinGW-W64...) version 8.1.0 (x86_64-w64-mingw32)
Trình điều khiển (driver)
gcc không phải là trình biên dịch. Nó là một trình điều khiển: nó nhìn phần mở rộng tệp và các cờ, rồi lần lượt gọi cpp, cc1, as, ld với đúng đối số. Trình biên dịch C thật sự là cc1.

#Xem macro và include

terminal
# In MOI macro tien dinh nghia cua trinh bien dich
gcc -std=c11 -dM -E - < /dev/null | wc -l
353
gcc -std=c11 -dM -E - < /dev/null | grep -E '__STDC_VERSION__|__GNUC__|__STDC__'
#define __GNUC__ 8
#define __STDC_VERSION__ 201112L
#define __STDC__ 1
terminal
# -H: in cay include, moi dau cham la mot muc long sau
gcc -std=c11 -H -E vd.c -o /dev/null 2>&1 | head -3
. .../include/stdio.h
.. .../include/crtdefs.h
... .../include/_mingw.h

Tự làm thử

  1. Chạy gcc -E trên một tệp có macro và tìm chỗ macro được thay.
  2. Viết một macro thiếu ngoặc, dùng -E để thấy nó sai thế nào.
  3. Dùng -save-temps và mở tệp .s, tìm tên một hàm bạn viết.
  4. Chạy gcc -v và tìm đường dẫn tới cc1 trên máy bạn.
  5. In mọi macro tiền định nghĩa và đếm bao nhiêu cái bắt đầu bằng __STDC.
  6. Dùng -H để đếm stdio.h kéo theo bao nhiêu header trên máy bạn.

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

  • Bốn giai đoạn: tiền xử lý, biên dịch, hợp dịch, liên kết; ra lần lượt .i, .s, .o, thực thi.
  • -E, -S, -c dừng gcc ở ba điểm; không có cờ thì chạy hết tới liên kết.
  • -save-temps chạy hết nhưng giữ lại mọi tệp trung gian.
  • gcc là trình điều khiển gọi cc1, as, ld; -v và -### cho thấy nó gọi gì.
  • -dM -E in mọi macro tiền định nghĩa, -H in cây include.