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

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

6 ответов

Решение

Создание функции 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 - это место для инкапсуляции; помимо сокрытия функции от других единиц перевода (точнее, из- за этого), создания функций static может также обеспечить выигрыш в производительности при наличии оптимизации компилятора.

Потому что static Функция не может быть вызвана нигде вне текущей единицы перевода (если код не берет указатель на ее адрес), компилятор контролирует все точки вызова в ней.

Это означает, что он может использовать нестандартный ABI, полностью его встроить или выполнять любое другое количество оптимизаций, которые могут быть невозможны для функции с внешним связыванием.

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

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

Глядя на посты выше, я хотел бы указать одну деталь.

Предположим, что наш основной файл ("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 не позволяет определить функцию в другом файле, отличном от того, где она объявлена.

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

Ответ 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.

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

Когда есть необходимость ограничить доступ к некоторым функциям, мы будем использовать ключевое слово 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 */
Другие вопросы по тегам