Điểm làm cho một hàm tĩnh trong C là gì?
Điểm làm cho một hàm tĩnh trong C là gì?
Câu trả lời:
Tạo một hàm static
ẩn nó khỏi các đơn vị dịch thuật khác, giúp cung cấp đóng gói .
helper_file.c
int f1(int); /* prototype */
static int f2(int); /* prototype */
int f1(int foo) {
return f2(foo); /* ok, f2 is in the same translation unit */
/* (basically same .c file) as f1 */
}
int f2(int foo) {
return 42 + foo;
}
chính.c :
int f1(int); /* prototype */
int f2(int); /* prototype */
int main(void) {
f1(10); /* ok, f1 is visible to the linker */
f2(12); /* nope, f2 is not visible to the linker */
return 0;
}
#include <helper_file.c>
không Tôi nghĩ rằng điều đó sẽ biến nó thành một đơn vị dịch thuật sau đó ...
gcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.c
. Các nguyên mẫu cho các chức năng có mặt trong cả hai tệp nguồn (không cần tệp tiêu đề). Trình liên kết sẽ giải quyết các chức năng.
pmg là tại chỗ về đóng gói; ngoài việc ẩn chức năng khỏi các đơn vị dịch thuật khác (hay đúng hơn là vì nó), làm cho các chức năng static
cũng có thể mang lại lợi ích hiệu suất khi có sự tối ưu hóa trình biên dịch.
Vì một static
hàm không thể được gọi từ bất kỳ đâu bên ngoài đơn vị dịch hiện tại (trừ khi mã đưa con trỏ đến địa chỉ của nó), trình biên dịch sẽ điều khiển tất cả các điểm gọi vào nó.
Điều này có nghĩa là có thể tự do sử dụng ABI không chuẩn, nội tuyến hoàn toàn hoặc thực hiện bất kỳ số lượng tối ưu hóa nào khác có thể không có cho một chức năng có liên kết ngoài.
static
chức năng thoát khỏi đơn vị dịch hiện tại, thì chức năng đó có thể được gọi trực tiếp từ các đơn vị dịch thuật khác.
Các static
từ khóa trong C được sử dụng trong một file biên dịch (.c như trái ngược với .h) để các chức năng chỉ tồn tại trong tập tin đó.
Thông thường, khi bạn tạo một hàm, trình biên dịch sẽ tạo ra hành trình mà trình liên kết có thể sử dụng để liên kết một lệnh gọi hàm đến hàm đó. Nếu bạn sử dụng từ khóa tĩnh, các hàm khác trong cùng một tệp có thể gọi hàm này (vì nó có thể được thực hiện mà không cần dùng đến trình liên kết), trong khi trình liên kết không có thông tin cho phép các tệp khác truy cập vào hàm.
Nhìn vào các bài viết ở trên tôi muốn chỉ ra một chi tiết.
Giả sử tệp chính của chúng tôi ("main.c") trông như thế này:
#include "header.h"
int main(void) {
FunctionInHeader();
}
Bây giờ hãy xem xét ba trường hợp:
Trường hợp 1: Tệp tiêu đề của chúng tôi ("header.h") trông như thế này:
#include <stdio.h>
static void FunctionInHeader();
void FunctionInHeader() {
printf("Calling function inside header\n");
}
Sau đó, lệnh sau trên linux:
gcc main.c header.h -o main
sẽ thành công ! Theo đó nếu một người chạy
./main
Đầu ra sẽ là
Chức năng gọi bên trong tiêu đề
Đó là những gì mà chức năng tĩnh nên in.
Trường hợp 2: Tệp tiêu đề của chúng tôi ("header.h") trông như thế này:
static void FunctionInHeader();
và chúng tôi cũng có thêm một tệp "header.c", trông như thế này:
#include <stdio.h>
#include "header.h"
void FunctionInHeader() {
printf("Calling function inside header\n");
}
Sau đó, lệnh sau
gcc main.c header.h header.c -o main
sẽ đưa ra một lỗi.
Trường hợp 3:
Tương tự như trường hợp 2, ngoại trừ bây giờ tệp tiêu đề của chúng tôi ("header.h") là:
void FunctionInHeader(); // keyword static removed
Sau đó, lệnh tương tự như trong trường hợp 2 sẽ thành công và tiếp tục thực thi ./main sẽ cho kết quả như mong đợi.
Vì vậy, từ các thử nghiệm này (được thực hiện trên máy Acer x86, Ubuntu OS) tôi đã đưa ra một giả định rằng
từ khóa tĩnh ngăn chức năng được gọi trong tệp * .c khác so với nơi được xác định.
Đúng nếu tôi đã sai lầm.
Các lập trình viên C sử dụng thuộc tính tĩnh để ẩn các khai báo biến và hàm bên trong các mô-đun, giống như bạn sẽ sử dụng các khai báo công khai và riêng tư trong Java và C ++. Tập tin nguồn C đóng vai trò của các mô-đun. Bất kỳ biến hoặc hàm toàn cục nào được khai báo với thuộc tính tĩnh là riêng tư đối với mô đun đó. Tương tự, bất kỳ biến hoặc hàm toàn cục nào được khai báo mà không có thuộc tính tĩnh là công khai và có thể được truy cập bởi bất kỳ mô-đun nào khác. Đó là thực hành lập trình tốt để bảo vệ các biến và chức năng của bạn với thuộc tính tĩnh bất cứ khi nào có thể.
Câu trả lời của pmg rất thuyết phục. Nếu bạn muốn biết cách khai báo tĩnh hoạt động ở cấp đối tượng thì thông tin dưới đây có thể thú vị với bạn. Tôi đã sử dụng lại cùng một chương trình được viết bởi pmg và biên dịch nó thành một tệp .so (đối tượng chia sẻ)
Nội dung sau đây là sau khi bỏ tập tin .so vào một cái gì đó mà con người có thể đọc được
0000000000000675 f1 : địa chỉ của chức năng F1
000000000000068c f2 : địa chỉ của hàm f2 (staticc)
lưu ý sự khác biệt trong địa chỉ chức năng, nó có nghĩa là một cái gì đó. Đối với một hàm được khai báo với các địa chỉ khác nhau, nó có thể biểu thị rất rõ rằng f2 sống rất xa hoặc trong một phân đoạn khác của tệp đối tượng.
Các trình liên kết sử dụng một cái gì đó gọi là PLT (bảng liên kết thủ tục) và GOT (bảng bù trừ toàn cầu) để hiểu các biểu tượng mà chúng có quyền truy cập để liên kết đến.
Hiện tại, hãy nghĩ rằng GOT và PLT liên kết một cách kỳ diệu tất cả các địa chỉ và một phần động chứa thông tin của tất cả các chức năng này được hiển thị bởi trình liên kết.
Sau khi bỏ phần động của tệp .so, chúng tôi nhận được một loạt các mục nhưng chỉ quan tâm đến chức năng F1 và f2 .
Phần động chỉ giữ mục nhập cho chức năng F1 tại địa chỉ 0000000000000675 và không dành cho f2 !
Num: Loại kích thước giá trị Bind Vis Ndx Name
9: 0000000000000675 23 FUNC GLOBAL DEFAULT 11 f1
Và đó là nó! Từ đó rõ ràng rằng trình liên kết sẽ không thành công trong việc tìm hàm f2 vì nó không nằm trong phần động của tệp .so.
Khi có nhu cầu hạn chế quyền truy cập vào một số chức năng, chúng tôi sẽ sử dụng từ khóa tĩnh trong khi xác định và khai báo một chức năng.
/* file ab.c */
static void function1(void)
{
puts("function1 called");
}
And store the following code in another file ab1.c
/* file ab1.c */
int main(void)
{
function1();
getchar();
return 0;
}
/* in this code, we'll get a "Undefined reference to function1".Because function 1 is declared static in file ab.c and can't be used in ab1.c */