статична функція в С


172

Який сенс зробити функцію статичною в C?


7
@nightcracker: У C ++ немає таких речей, як "методи". Думаю, ви плутаєте об'єктив-C.
Бо Персон

1
Ні, я плутаю Пітона. Функція всередині класу називається методом в Python.
orlp

Відповіді:


212

Виконання функції staticприховує її від інших блоків перекладу, що допомагає забезпечити інкапсуляцію .

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

main.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
Чи є тут блок перекладу правильною термінологією? Невже об'єктний файл не буде більш точним? Як я розумію, статична функція прихована від лінкера, і лінкер не працює на одиницях перекладу.
Стівен Екхофф

2
Я також повинен був сказати, що мені подобається думати про це як про прихованому від лінкера; здається, ясніше це.
Стівен Екхофф

1
Отже, внутрішню функцію (що ми впевнені, що не називатимемо її поза своїм файлом c), ми повинні ставити її як статичну функцію, правда? Отже, ми можемо впевнитись, що він не може дзвонити в інше місце. Дякую :)
hqt

1
Як ви це складаєте? Ви використовуєте #include <helper_file.c>? Я думаю, що це зробило б тоді єдину перекладацьку одиницю ...
Atcold

2
@Atcold: як я написав код, ти просто включиш 2 вихідних файли в командний рядок, як це gcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.c. Прототипи функцій є в обох вихідних файлах (немає необхідності у заголовкових файлах). Лінкер вирішить функції.
pmg

80

pmg - пляма щодо інкапсуляції; за приховування функції від інших одиниць перекладу (або , скоріше, з - за цього), що виконує функції staticтакож може радитися перевага продуктивності в присутності оптимізації компілятора.

Оскільки staticфункцію неможливо викликати з будь-якого місця поза поточним блоком перекладу (якщо код не вказує на свою адресу), компілятор контролює всі точки виклику до нього.

Це означає, що вільно використовувати нестандартний ABI, повністю вбудовувати його або виконувати будь-яку кількість інших оптимізацій, які можуть бути неможливими для функції із зовнішнім зв’язком.


9
... якщо тільки не взята адреса функції.
caf

1
@caf Що ви маєте на увазі під адресою функції? Для мене поняття функцій / змінної, що мають адреси або присвоєну адресу під час компіляції, є дещо заплутаним. Чи можете ви, будь ласка, докладно?
SayeedHussain

2
@crypticcoder: Ваша програма завантажена в пам'ять, тому функції також мають місце в пам'яті і адресу можна отримати. За допомогою вказівника функції ви можете викликати будь-який із них. Якщо ви це зробите, це зменшує перелік оптимізацій, які може виконувати компілятор, оскільки код повинен залишатися недоторканим у тому самому місці.

5
@crypticcoder: Я маю на увазі, що вираз оцінює вказівник на функцію і робить щось із нею, крім одразу виклику функції. Якщо вказівник на staticфункцію уникає поточної одиниці перекладу, то цю функцію можна безпосередньо викликати з інших одиниць перекладу.
caf

@caf, якщо адреса функції взята, чи буде компілятор виявляти це та вимикати статичні оптимізації функції, згадані у цій відповіді (наприклад, використовуючи нестандартний ABI)? Я гадаю, що це доведеться.
sevko

28

staticКлючове слово в C використовується в скомпільований файл (.c на відміну від .h) , так що функція існує тільки в цьому файлі.

Зазвичай, коли ви створюєте функцію, компілятор генерує суворе, яке може використовувати лінкер, а також зв'язувати виклик функції з цією функцією. Якщо ви використовуєте статичне ключове слово, інші функції в тому ж файлі можуть викликати цю функцію (оскільки це можна зробити, не вдаючись до лінкера), в той час як у лінкера немає інформації, що дозволяє іншим файлам отримувати доступ до функції.


1
3Doub: Використання слова "cruft" є більш точним, ніж ви йому належите. У контексті запитання "cruft" - це правильне слово, яке тут використовується.
Ерік Аронесті

@ 3Doubloons Я погоджуюся, що це спрощено, але я думаю, що це набагато простіше зрозуміти для початківців.
Інго Бюрк

11

Розглядаючи повідомлення вище, я хотів би вказати одну деталь.

Припустимо, наш основний файл ("main.c") виглядає приблизно так:

#include "header.h"

int main(void) {
    FunctionInHeader();
}

Тепер розглянемо три випадки:

  • Випадок 1: Наш файл заголовка ("header.h") виглядає так:

    #include <stdio.h>
    
    static void FunctionInHeader();
    
    void FunctionInHeader() {
        printf("Calling function inside header\n");
    }

    Потім наступна команда на Linux:

    gcc main.c header.h -o main

    вдасться ! Слідом за цим, якщо один біжить

    ./main

    Вихід буде

    Функція виклику всередині заголовка

    Це те, що повинна друкувати ця статична функція.

  • Випадок 2: Наш файл заголовка ("header.h") виглядає так:

    static void FunctionInHeader();     

    а також у нас є ще один файл "header.c", який виглядає приблизно так:

    #include <stdio.h>
    
    #include "header.h"
    
    void FunctionInHeader() {
        printf("Calling function inside header\n");
    }

    Потім наступна команда

    gcc main.c header.h header.c -o main

    дасть помилку

  • Випадок 3:

    Аналогічно випадку 2, за винятком того, що тепер наш заголовок ("header.h"):

    void FunctionInHeader(); // keyword static removed

    Тоді така ж команда, як у випадку 2, буде успішною, і подальше виконання ./main дасть очікуваний результат.

Тому з цих тестів (виконаних на Acer x86 machine, Ubuntu OS) я зробив припущення, що

статичне ключове слово запобігає виклику функції в інший * .c файл, ніж там, де це визначено.

Виправте мене, якщо я помиляюся.


5

Програмісти C використовують статичний атрибут, щоб приховати декларації змінних та функцій всередині модулів, наскільки ви б використовували публічні та приватні декларації в Java та C ++. Файли з джерелом C відіграють роль модулів. Будь-яка глобальна змінна чи функція, оголошена статичним атрибутом, є приватною для цього модуля. Аналогічно, будь-яка глобальна змінна чи функція, оголошена без статичного атрибуту, є загальнодоступною та може отримати доступ до будь-якого іншого модуля. Це хороша практика програмування, щоб захистити свої змінні та функції статичним атрибутом, де це можливо.


4

Відповідь pmg дуже переконлива. Якщо ви хочете дізнатися, як працюють статичні декларації на рівні об'єктів, то ця інформація нижче може бути цікавою для вас. Я повторно використовував ту саму програму, яку написав pmg, і компілював її у файл .so (спільний об’єкт)

Наступний вміст знаходиться після скидання .so файлу в щось читабельне для людини

0000000000000675 f1 : адреса функції f1

000000000000068c f2 : адреса функції f2 (статична)

зауважте різницю в адресі функції, це щось означає. Для функції, яка оголошена з іншою адресою, вона може дуже добре означати, що f2 живе дуже далеко або в іншому сегменті файлу об'єкта.

Лінкери використовують те, що називається PLT (таблиця зв’язків процедур) та GOT (Глобальна таблиця зрушень), щоб зрозуміти символи, до яких вони мають доступ.

Поки думаєте, що GOT і PLT магічно прив'язують усі адреси, а динамічний розділ містить інформацію про всі ці функції, які видно лінкером.

Після демпінгу динамічного розділу файлу .so ми отримуємо купу записів, але їх цікавить лише функція f1 та f2 .

Динамічний розділ містить запис лише для функції f1 за адресою 0000000000000675, а не для f2 !

Число: Значення Розмір Тип Прив’язати Ім’я Vis Ndx

 9: 0000000000000675    23 FUNC    GLOBAL DEFAULT   11 f1

І це все !. З цього видно, що лінкер буде невдалий у пошуку функції f2, оскільки його немає в динамічному розділі файлу .so.


0

Коли виникає потреба обмежити доступ до деяких функцій, ми будемо використовувати статичне ключове слово, визначаючи та оголошуючи функцію.

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

Ця відповідь не дуже допомагає.
fiscblog
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.