Статическая функция в C
В чем смысл сделать функцию static в C?
Ответы
Ответ 1
Выполнение функции static
скрывает ее от других единиц перевода, что помогает encapsulation.
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;
}
Ответ 2
pmg указывает на инкапсуляцию; помимо скрытия функции от других единиц перевода (или, скорее, из-за нее), выполнение функций static
также может принести преимущества производительности при наличии оптимизаций компилятора.
Поскольку функция static
не может быть вызвана нигде вне текущей единицы перевода (если код не принимает указатель на его адрес), компилятор контролирует все точки вызова в нем.
Это означает, что он может свободно использовать нестандартный ABI, встроить его полностью или выполнить любое количество других оптимизаций, которые могут быть недоступны для функции с внешней связью.
Ответ 3
Ключевое слово static
в C используется в скомпилированном файле (.c в противоположность .h), так что функция существует только в этом файле.
Обычно, когда вы создаете функцию, компилятор генерирует кривую, которую может использовать компоновщик, ну, связать вызов функции с этой функцией. Если вы используете статическое ключевое слово, другие функции в одном файле могут вызвать эту функцию (потому что это можно сделать, не прибегая к компоновщику), в то время как у компоновщика нет информации, позволяющей другим файлам получить доступ к этой функции.
Ответ 4
Глядя на сообщения выше, я хотел бы указать одну деталь.
Предположим, что наш основной файл ( "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 OS) я сделал предположение, что
статическое ключевое слово предотвращает определение функции в другом файле, чем там, где она объявлена.
Исправьте меня, если я ошибаюсь.
Ответ 5
Программисты C используют статический атрибут для скрытия объявлений переменных и функций внутри модулей,
так как вы использовали бы публичные и частные объявления в Java и С++. C исходных файлов играют роль
модули. Любая глобальная переменная или функция, объявленная со статическим атрибутом, является частной для этого модуля.
Аналогично, любая глобальная переменная или функция, объявленная без статического атрибута, является общедоступной и может быть
доступ к которому осуществляется любым другим модулем. Хорошая практика программирования защищает ваши переменные и функции
со статическим атрибутом, где это возможно.
Ответ 6
pmg ответ очень убедителен. Если вы хотите узнать, как работают статические объявления на уровне объекта, эта информация может быть вам интересна.
Я повторно использовал ту же программу, написанную pmg, и компилятор в файл .so(shared object)
Следующее содержимое после сброса файла .so в нечто доступно для людей
0000000000000675 f1: адрес функции f1
000000000000068c f2: адрес функции f2 (staticc)
Обратите внимание на разницу в адресе функции, это что-то значит. Для функции, объявленной с другим адресом, это может очень хорошо означать, что f2 живет очень далеко или в другом сегменте объектного файла.
Линкеры используют что-то, называемое PLT (таблица связывания процедур) и GOT (глобальная таблица смещений), чтобы понимать символы, к которым у них есть доступ к ссылке.
Теперь подумайте, что GOT и PLT магически связывают все адреса, а динамический раздел содержит информацию обо всех этих функциях, которые видны компоновщиком.
После демпинга динамического раздела .so файла мы получаем кучу записей, но интересуемся только функциями f1 и f2.
Динамический раздел содержит запись только для функции f1 по адресу 0000000000000675, а не для f2!
Num: значение Размер Тип Bind Vis Ndx Name
9: 0000000000000675 23 FUNC GLOBAL DEFAULT 11 f1
И вот оно! Из этого ясно, что компоновщик не удастся найти функцию f2, поскольку она не находится в динамическом разделе .so файла.