Пример минимальной работоспособной многофайловой области
Здесь я проиллюстрирую, как static
влияет область определения функций в нескольких файлах.
переменный ток
#include <stdio.h>
/* Undefined behavior: already defined in main.
* Binutils 2.24 gives an error and refuses to link.
* /programming/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 вверх по течению .
Скомпилируйте и запустите:
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 ++ вы можете захотеть использовать анонимные пространства имен вместо статических, что дает аналогичный эффект, но дополнительно скрывает определения типов: Безымянный / анонимный пространства имен против статических функций