Пример минимальной работоспособной многофайловой области действия
Здесь я иллюстрирую, как static
влияет на область определения функций в нескольких файлах.
a.c
#include <stdio.h>
/* Undefined behavior: already defined in main.
* Binutils 2.24 gives an error and refuses to link.
* https://stackoverflow.com/questions/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
*/
/*void f() { puts("a f"); }*/
/* OK: only declared, not defined. Will use the one in main. */
void f(void);
/* OK: only visible to this file. */
static void sf() { puts("a sf"); }
void a() {
f();
sf();
}
main.c
#include <stdio.h>
void a(void);
void f() { puts("main f"); }
static void sf() { puts("main sf"); }
void m() {
f();
sf();
}
int main() {
m();
a();
return 0;
}
GitHub upstream .
Скомпилируйте и запустите:
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main
Выход:
main f
main sf
main f
a sf
Интерпретация
- есть две отдельные функции
sf
, по одной для каждого файла
- есть одна общая функция
f
Как обычно, чем меньше область действия, тем лучше, поэтому всегда объявляйте функции static
, если можете.
В программировании на C файлы часто используются для представления «классов», а функции static
представляют «частные» методы класса.
Обычный шаблон C - это передача структуры this
в качестве первого аргумента «метода», что в основном и делает C ++ под капотом.
Что говорят об этом стандарты
C99 N1256 черновик 6.7.1 «Спецификаторы класса хранения» говорят, что static
является «спецификатором класса хранения».
6.2.2 / 3 «Связи идентификаторов» говорит static
подразумевает internal linkage
:
Если объявление идентификатора области файла для объекта или функции содержит статический спецификатор класса хранения, идентификатор имеет внутреннюю связь.
и 6.2.2 / 2 говорят, что internal linkage
ведет себя так, как в нашем примере:
В наборе единиц перевода и библиотек, составляющих целую программу, каждое объявление определенного идентификатора с внешней связью обозначает один и тот же объект или функцию. В пределах одной единицы перевода каждое объявление идентификатора с внутренней связью обозначает один и тот же объект или функцию.
где «единица перевода» - исходный файл после предварительной обработки.
Как GCC реализует это для ELF (Linux)?
С привязкой STB_LOCAL
.
Если мы скомпилируем:
int f() { return 0; }
static int sf() { return 0; }
и разберите таблицу символов с помощью:
readelf -s main.o
вывод содержит:
Num: Value Size Type Bind Vis Ndx Name
5: 000000000000000b 11 FUNC LOCAL DEFAULT 1 sf
9: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 f
поэтому связывание - единственное существенное различие между ними. Value
- это просто их смещение в секции .bss
, поэтому мы ожидаем, что оно будет другим.
STB_LOCAL
задокументировано в спецификации ELF на http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html:
STB_LOCAL Локальные символы не видны за пределами объектного файла, содержащего их определение. Локальные символы с одинаковыми именами могут существовать в нескольких файлах, не мешая друг другу
, что делает его идеальным выбором для представления static
.
Функции без статики STB_GLOBAL
, а в спецификации написано:
Когда редактор ссылок объединяет несколько перемещаемых объектных файлов, он не позволяет использовать несколько определений символов STB_GLOBAL с одним и тем же именем.
, что согласуется с ошибками связи в нескольких нестатических определениях.
Если мы запускаем оптимизацию с помощью -O3
, символ sf
полностью удаляется из таблицы символов: его нельзя использовать извне. TODO зачем вообще хранить статические функции в таблице символов, когда нет оптимизации? Могут ли они быть использованы для чего-либо?
См. Также
C ++ анонимные пространства имен
В C ++ вы можете использовать анонимные пространства имен вместо статических, что обеспечивает аналогичный эффект, но дополнительно скрывает определения типов: Безымянные / анонимные пространства имен и статические функции