статическая функция в C

172

Какой смысл делать статическую функцию в C?

Cenoc
источник
7
@nightcracker: В C ++ нет таких вещей, как «методы». Я думаю, что вы запутались с Objective-C.
Бо Перссон
1
Нет, я запутался с Питоном. Функция внутри класса называется методом в Python.
orlp
6
Возможный дубликат Что такое «статическая» функция? (в С)
atoMerz

Ответы:

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;
}
PMG
источник
8
Является ли единица перевода правильной терминологией для использования здесь? Разве объектный файл не будет более точным? Из того, что я понимаю, статическая функция скрыта от компоновщика, и компоновщик не работает с модулями перевода.
Стивен Экхофф
2
Я должен был также сказать, что мне нравится думать, что это скрыто от компоновщика; это кажется более понятным.
Стивен Экхофф
1
Итак, внутреннюю функцию (чтобы мы не вызывали ее вне ее c-файла), мы должны поместить ее как статическую функцию, верно? Таким образом, мы можем быть уверены, что он не может позвонить в другом месте. Спасибо :)
14:30
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
... если не указан адрес функции.
Кафе
1
@caf Что вы подразумеваете под адресом функции? Для меня понятие функций / переменных, имеющих адреса или назначенный адрес во время компиляции, немного сбивает с толку. Можете ли вы уточнить?
SayeedHussain
2
@crypticcoder: ваша программа загружена в память, поэтому функции также имеют место в памяти и адрес может быть получен. С помощью указателя на функцию вы можете вызвать любой из них. Если вы это сделаете, это сократит список оптимизаций, которые может выполнить компилятор, поскольку код должен оставаться неизменным в одном и том же месте.
5
@crypticcoder: я имею в виду, что выражение оценивает указатель на функцию и делает с ней что-то еще, кроме немедленного вызова функции. Если указатель на staticфункцию выходит за пределы текущей единицы перевода, то эта функция может быть вызвана напрямую из других единиц перевода.
Кафе
@caf, если адрес функции взят, будет ли компилятор обнаруживать это и отключать статические функции оптимизации, упомянутые в этом ответе (например, с использованием нестандартного ABI)? Я предполагаю, что это должно было бы
севко
28

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

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

3Doubloons
источник
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, ОС Ubuntu) я сделал предположение, что

Ключевое слово static не позволяет вызывать функцию в другом файле * .c, чем тот, в котором она определена.

Поправь меня, если я ошибаюсь.

mercury0114
источник
5

Программисты C используют статический атрибут, чтобы скрыть объявления переменных и функций внутри модулей, так же, как вы используете публичные и частные объявления в Java и C ++. C исходные файлы играют роль модулей. Любая глобальная переменная или функция, объявленная со статическим атрибутом, является частной для этого модуля. Точно так же любая глобальная переменная или функция, объявленная без статического атрибута, является общедоступной и может быть доступна любому другому модулю. Хорошей практикой программирования является защита ваших переменных и функций статическим атрибутом, где это возможно.

Компьютерные системы
источник
4

Ответ PMG очень убедителен. Если вы хотите знать, как работают статические объявления на уровне объектов, эта информация может быть вам интересна. Я повторно использовал ту же программу, написанную pmg, и скомпилировал ее в файл .so (общий объект)

Следующее содержание после сброса .so файла в нечто читаемое человеком

0000000000675 f1 : адрес функции f1

00000000006868c f2 : адрес функции f2 (staticc)

обратите внимание на разницу в адресе функции, это что-то значит. Для функции, которая объявлена ​​с другим адресом, это может очень хорошо показать, что f2 живет очень далеко или в другом сегменте объектного файла.

Линкеры используют нечто, называемое PLT (таблица связывания процедур) и GOT (глобальная таблица смещений), чтобы понимать символы, к которым у них есть доступ к ссылкам.

А пока подумайте, что GOT и PLT магически связывают все адреса, а динамический раздел содержит информацию обо всех этих функциях, которые видны компоновщику.

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

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

Num: Значение Размер Тип Bind Vis Ndx Имя

 9: 0000000000000675    23 FUNC    GLOBAL DEFAULT   11 f1

И это все !. Из этого ясно, что компоновщик не сможет найти функцию f2, поскольку ее нет в динамическом разделе файла .so.

human.js
источник
0

Когда необходимо ограничить доступ к некоторым функциям, мы будем использовать ключевое слово static при определении и объявлении функции.

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