hàm tĩnh trong C


172

Điểm làm cho một hàm tĩnh trong C là gì?


7
@nightcracker: Không có những thứ như "phương thức" trong C ++. Tôi nghĩ bạn đang nhầm lẫn với Objective-C.
Bo Persson

1
Không, tôi bối rối với Python. Một hàm bên trong một lớp được gọi là một phương thức trong Python.
orlp

Câu trả lời:


212

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;
}

8
Là đơn vị dịch thuật thuật ngữ chính xác để sử dụng ở đây? Tập tin đối tượng sẽ không chính xác hơn? Theo những gì tôi hiểu, một hàm tĩnh được ẩn khỏi trình liên kết và trình liên kết không hoạt động trên các đơn vị dịch thuật.
Steven Eckhoff

2
Tôi cũng nên nói rằng, tôi thích nghĩ về nó như bị ẩn khỏi liên kết; Nó có vẻ rõ ràng hơn theo cách đó.
Steven Eckhoff

1
vì vậy, hàm nội bộ (mà chúng ta chắc chắn không gọi nó bên ngoài tệp c của nó), chúng ta nên đặt nó dưới dạng hàm tĩnh, phải không? Vì vậy, chúng tôi có thể chắc chắn rằng nó không thể gọi ở nơi khác. Cảm ơn :)
hqt

1
Làm thế nào để bạn biên dịch này? Bạn có dùng #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 đó ...
Vào

2
@Atcold: cách tôi viết mã bạn chỉ cần bao gồm 2 tệp nguồn trong dòng lệnh, như thế này 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

80

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à nó), làm cho các chức năng staticcũ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 statichà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.


9
... Trừ khi địa chỉ của hàm được lấy.
phê

1
@caf Bạn có ý nghĩa gì bởi địa chỉ của chức năng được lấy? Đối với tôi, khái niệm hàm / biến có địa chỉ hoặc được gán địa chỉ tại thời gian biên dịch là một chút khó hiểu. Bạn có thể vui lòng giải thích?
SayeedHussain

2
@crypticcoder: Chương trình của bạn được tải trong bộ nhớ, do đó các chức năng cũng có một vị trí bộ nhớ và địa chỉ có thể được lấy. Với một con trỏ hàm, bạn có thể gọi bất kỳ trong số đó. Nếu bạn làm điều đó, nó sẽ làm giảm danh sách tối ưu hóa mà trình biên dịch có thể thực hiện do mã phải được giữ nguyên ở cùng một vị trí.

5
@crypticcoder: Ý tôi là một biểu thức đánh giá một con trỏ tới hàm và thực hiện một cái gì đó với nó ngoài việc gọi hàm ngay lập tức. Nếu một con trỏ tới một staticchứ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.
phê

@caf nếu lấy địa chỉ của hàm, trình biên dịch có phát hiện ra điều đó không và tắt tối ưu hóa hàm tĩnh được đề cập trong câu trả lời này (ví dụ: sử dụng ABI không chuẩn)? Tôi cho rằng nó sẽ phải.
Sevko

28

Các statictừ 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.


1
3Doub: Việc sử dụng từ "cruft" chính xác hơn là bạn cho nó tín dụng. Trong ngữ cảnh của câu hỏi, "cruft" là từ thích hợp để sử dụng ở đây.
Erik Aronesty

@ 3Doubloons Tôi đồng ý rằng nó được đơn giản hóa, nhưng tôi nghĩ rằng điều đó làm cho nó dễ hiểu hơn nhiều đối với người mới bắt đầu.
Ingo Bürk

11

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.


5

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ể.


4

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 F1f2 .

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.


0

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 */

Câu trả lời này không hữu ích lắm.
fiscblog
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.