Ví dụ phạm vi đa tệp có thể chạy tối thiểu
Ở đây tôi minh họa cách static
ảnh hưởng đến phạm vi định nghĩa hàm trên nhiều tệp.
AC
#include <stdio.h>
/* Undefined behavior: already defined in main.
* Binutils 2.24 gives an error and refuses to link.
* /programming/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
*/
/*void f() { puts("a f"); }*/
/* OK: only declared, not defined. Will use the one in main. */
void f(void);
/* OK: only visible to this file. */
static void sf() { puts("a sf"); }
void a() {
f();
sf();
}
C chính
#include <stdio.h>
void a(void);
void f() { puts("main f"); }
static void sf() { puts("main sf"); }
void m() {
f();
sf();
}
int main() {
m();
a();
return 0;
}
GitHub ngược dòng .
Biên dịch và chạy:
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main
Đầu ra:
main f
main sf
main f
a sf
Diễn dịch
- Có hai chức năng riêng biệt
sf
, một chức năng cho mỗi tệp
- có một chức năng chia sẻ duy nhất
f
Như thường lệ, phạm vi càng nhỏ thì càng tốt, vì vậy luôn luôn khai báo các hàm static
nếu bạn có thể.
Trong lập trình C, các tệp thường được sử dụng để đại diện cho "các lớp" và các static
hàm đại diện cho các phương thức "riêng tư" của lớp.
Một mẫu C phổ biến là truyền một this
cấu trúc xung quanh làm đối số "phương thức" đầu tiên, về cơ bản là những gì C ++ thực hiện dưới mui xe.
Những tiêu chuẩn nói về nó
C99 N1256 dự thảo 6.7.1 "Trình xác định lớp lưu trữ" nói rằng đó static
là "trình xác định lớp lưu trữ".
6.2.2 / 3 "Liên kết của định danh" cho biết static
ngụ ý internal linkage
:
Nếu khai báo của một định danh phạm vi tệp cho một đối tượng hoặc một hàm chứa định danh lớp lưu trữ tĩnh, thì định danh có liên kết bên trong.
và 6.2.2 / 2 nói rằng internal linkage
hành xử như trong ví dụ của chúng tôi:
Trong tập hợp các đơn vị dịch thuật và thư viện cấu thành toàn bộ chương trình, mỗi khai báo của một mã định danh cụ thể có liên kết bên ngoài biểu thị cùng một đối tượng hoặc chức năng. Trong một đơn vị dịch thuật, mỗi khai báo của một định danh có liên kết bên trong biểu thị cùng một đối tượng hoặc chức năng.
trong đó "đơn vị dịch" là một tệp nguồn sau khi tiền xử lý.
GCC triển khai nó như thế nào cho ELF (Linux)?
Với sự STB_LOCAL
ràng buộc.
Nếu chúng tôi biên dịch:
int f() { return 0; }
static int sf() { return 0; }
và tháo rời bảng biểu tượng với:
readelf -s main.o
đầu ra chứa:
Num: Value Size Type Bind Vis Ndx Name
5: 000000000000000b 11 FUNC LOCAL DEFAULT 1 sf
9: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 f
vì vậy sự ràng buộc là sự khác biệt đáng kể duy nhất giữa chúng. Value
chỉ là phần bù của họ vào .bss
phần này, vì vậy chúng tôi hy vọng nó sẽ khác.
STB_LOCAL
được ghi lại trên thông số ELF tại http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :
Các biểu tượng cục bộ không thể nhìn thấy bên ngoài tệp đối tượng chứa định nghĩa của chúng. Các ký hiệu cục bộ cùng tên có thể tồn tại trong nhiều tệp mà không can thiệp lẫn nhau
mà làm cho nó một sự lựa chọn hoàn hảo để đại diện static
.
Các chức năng không có tĩnh là STB_GLOBAL
, và thông số kỹ thuật nói:
Khi trình soạn thảo liên kết kết hợp một số tệp đối tượng có thể định vị lại, nó không cho phép nhiều định nghĩa của các ký hiệu STB_GLOBAL có cùng tên.
phù hợp với các lỗi liên kết trên nhiều định nghĩa không tĩnh.
Nếu chúng tôi tăng cường tối ưu hóa -O3
, sf
biểu tượng sẽ bị xóa hoàn toàn khỏi bảng biểu tượng: không thể sử dụng nó từ bên ngoài. TODO tại sao vẫn giữ các chức năng tĩnh trên bảng biểu tượng khi không có tối ưu hóa? Chúng có thể được sử dụng cho bất cứ điều gì?
Xem thêm
Không gian tên ẩn danh C ++
Trong C ++, bạn có thể muốn sử dụng các không gian tên ẩn danh thay vì tĩnh, điều này đạt được hiệu ứng tương tự, nhưng ẩn thêm các định nghĩa kiểu: Không gian tên ẩn danh / ẩn danh so với các hàm tĩnh