Bài 20.328 phút đọc
Liên kết trong và liên kết ngoài
Sau bài này bạn sẽ làm được
- Dùng static để giấu hàm và biến khỏi các tệp khác
- Khai báo biến toàn cục dùng chung đúng cách bằng extern
- Giải thích định nghĩa tạm thời và khác biệt giữa fcommon với fno-common
- Đọc bảng ký hiệu bằng nm và nhận ra ký hiệu trong với ký hiệu ngoài
Trong C, mọi hàm và biến toàn cục mặc định đều nhìn thấy được từ mọi tệp khác trong chương trình. Đó là mặc định sai, và static là một từ khóa duy nhất sửa nó.
#Hai loại liên kết
Liên kết ngoài
Tên được xuất ra bảng ký hiệu, nên tệp khác tham chiếu tới được. Đây là mặc định cho hàm và biến khai báo ở phạm vi tệp.
Liên kết trong
Tên chỉ tồn tại trong đơn vị dịch của nó. Tệp khác không thấy, và hai tệp được phép có hai thứ trùng tên mà không xung đột. Đạt được bằng từ khóa
static ở phạm vi tệp.sinhvien.c
#include "sinhvien.h"
/* --- LIEN KET NGOAI: tep khac dung duoc --- */
int g_so_sv = 0; /* bien toan cuc */
MaLoi sv_kiem_mssv(const char *s) { ... } /* ham cong khai */
/* --- LIEN KET TRONG: chi sinhvien.c thay --- */
static int s_bo_dem = 0; /* bien cap tep */
static void chuan_hoa(char *s) { ... } /* ham noi bo */| Khai báo ở phạm vi tệp | Liên kết | Tệp khác thấy không |
|---|---|---|
| int x; | Ngoài | Có |
| int x = 5; | Ngoài | Có |
| static int x; | Trong | Không |
| void f(void) { } | Ngoài | Có |
| static void f(void) { } | Trong | Không |
| static inline void f(void) { } | Trong | Không |
| const int x = 5; | Ngoài trong C | Có, khác C++ |
#static ở phạm vi tệp
terminal
cat s1.c
static int s_dem = 0;
static void phu(void) { ++s_dem; }
int cong_khai(void) { phu(); return s_dem; }cat s2.c
static int s_dem = 100; /* CUNG TEN, khong xung dot */
int cong_khai(void);
int main(void) { return cong_khai() + s_dem - 101; }gcc -std=c11 -fno-common -o s s1.c s2.c && ./s; echo "exit=$?"
exit=0
terminal
# Hàm static không ai gọi thì được cảnh báo
gcc -Wall -Wextra -c sinhvien.c
sinhvien.c:88:13: warning: 'ham_cu' defined but not used [-Wunused-function]
88 | static void ham_cu(void) {
| ^~~~~~Biến static cấp tệp thay cho biến toàn cục
Biến toàn cục công khai
/* quanly.h */
extern QuanLy g_quanly; /* moi tep deu sua duoc */
/* quanly.c */
QuanLy g_quanly;
/* Bat cu tep nao cung viet duoc:
g_quanly.n = 0;
va ban khong bao gio biet ai da lam hong no. */Giấu sau hàm truy cập
/* quanly.h: KHONG lo bien ra ngoai */
MaLoi ql_them(const SinhVien *sv);
size_t ql_so_luong(void);
/* quanly.c */
static QuanLy s_quanly; /* chi tep nay cham vao duoc */
MaLoi ql_them(const SinhVien *sv) {
/* ... sua s_quanly ... */
}
size_t ql_so_luong(void) { return s_quanly.n; }#extern cho biến dùng chung
Mẫu chuẩn, ba tệp
/* cau-hinh.h */
#ifndef CAU_HINH_H
#define CAU_HINH_H
extern int g_muc_ghi; /* KHAI BAO */
extern const char *g_ten_app;
#endif
/* cau-hinh.c */
#include "cau-hinh.h"
int g_muc_ghi = 2; /* DINH NGHIA, dung mot lan */
const char *g_ten_app = "Quan ly sinh vien";
/* main.c */
#include "cau-hinh.h"
int main(void) {
g_muc_ghi = 4;
printf("%s\n", g_ten_app);
return 0;
}extern cho mảng: cẩn thận kích thước
/* bang.c */
const int BANG[] = { 1, 2, 3, 4, 5 };
/* bang.h */
extern const int BANG[]; /* khong co kich thuoc */
/* nguoi dung */
#include "bang.h"
size_t n = sizeof BANG / sizeof BANG[0]; /* LOI:
error: invalid application of 'sizeof' to incomplete type */
/* Cach chua 1: ghi ro kich thuoc trong header */
extern const int BANG[5];
/* Cach chua 2: xuat them so luong, an toan hon */
extern const int BANG[];
extern const size_t BANG_SO_LUONG;
/* bang.c */
const size_t BANG_SO_LUONG = sizeof BANG / sizeof BANG[0];#Định nghĩa tạm thời và fno-common
Định nghĩa tạm thời
Khai báo biến ở phạm vi tệp, không có
extern và không có giá trị đầu, ví dụ int x;. Chuẩn C coi đây là một trường hợp riêng: nếu trong cùng tệp có định nghĩa thật thì nó là khai báo, còn không thì nó thành định nghĩa với giá trị không.terminal
# Header có int g_dem; không extern, hai tệp .c include
gcc -std=c11 -fcommon -o za z1.c z2.c && ./za; echo "exit=$?"
exit=0
gcc -std=c11 -fno-common -o zb z1.c z2.c
z2.o:z2.c:(.bss+0x0): multiple definition of `g_dem' z1.o:z1.c:(.bss+0x0): first defined here collect2: error: ld returned 1 exit status
| Trong header | Với -fcommon | Với -fno-common |
|---|---|---|
| extern int x; | Chạy | Chạy |
| int x; | Chạy, gộp lại thành một | Lỗi multiple definition |
| int x = 0; | Lỗi | Lỗi |
#Đọc bảng ký hiệu bằng nm
terminal
cat nmtest.c
static int s_dem_tinh = 0;
static int s_dem_khong;
int g_co_dau = 5;
int g_khong_dau;
const int G_HANG = 7;
static void phu(void) { ++s_dem_tinh; }
int cong_khai(void) { ... }
int can_ngoai(const char *a, const char *b) { return strcmp(a, b); }gcc -std=c11 -fno-common -c nmtest.c && nm nmtest.o
0000000000000049 T can_ngoai
0000000000000011 T cong_khai
0000000000000000 D g_co_dau
0000000000000000 R G_HANG
0000000000000000 B g_khong_dau
0000000000000000 t phu
0000000000000008 b s_dem_khong
0000000000000004 b s_dem_tinh
U strcmp| Chữ | Nghĩa | Sinh ra từ |
|---|---|---|
| T | Mã lệnh, liên kết ngoài | Hàm thường |
| t | Mã lệnh, liên kết trong | Hàm static |
| D | Dữ liệu có giá trị đầu, ngoài | int g = 5; |
| d | Dữ liệu có giá trị đầu, trong | static int g = 5; |
| B | Dữ liệu bằng không, ngoài | int g; |
| b | Dữ liệu bằng không, trong | static int g; |
| R | Chỉ đọc, ngoài | const int g = 5; |
| U | CHƯA CÓ, tệp này đang cần | Gọi hàm ở tệp khác |
| C | Common, định nghĩa tạm thời | int g; với -fcommon |
terminal
# Cùng tệp đó, đổi sang -fcommon
gcc -std=c11 -fcommon -c nmtest.c && nm nmtest.o | grep khong_dau
0000000000000004 C g_khong_dau
Tự làm thử
- Viết hai tệp
.ccùng cóstatic int s_demvới hai giá trị khác nhau, liên kết lại và xác nhận không xung đột. - Bỏ
staticđi ở cả hai và xem thông báo lỗi. - Đặt
int g;khôngexterntrong header, include từ hai tệp, rồi thử cả-fcommonvà-fno-common. - Chạy
nmtrên một tệp.ocủa bạn và phân loại từng ký hiệu theo bảng ở trên. - Đếm số ký hiệu module của bạn xuất ra và so với số hàm trong header, rồi thêm
staticcho phần chênh lệch. - Chạy
nm -utrên một tệp.ovà giải thích vì sao mỗi ký hiệuUlại có mặt.
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
- Mặc định của C là liên kết ngoài, tức là mọi hàm và biến toàn cục đều lộ ra cho cả chương trình.
- Nguyên tắc đóng gói: mọi hàm và biến không khai báo trong
.hđều phảistatic. staticở phạm vi tệp nghĩa là liên kết trong, cònstatictrong hàm nghĩa là thời gian sống tĩnh. Hai chuyện khác nhau.- Biến toàn cục dùng chung:
externtrong header, định nghĩa trong đúng một tệp.c. int x;trong header chạy được với-fcommonnhưng lỗi với-fno-common, và mặc định đã đổi từ GCC 10.nmcho biết module xuất ra gì và cần gì. Chữ hoa là liên kết ngoài, chữ thường là liên kết trong,Ulà đang thiếu.