Пример области действия нескольких файлов
Здесь я иллюстрирую, как 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
*/
/*int i = 0;*/
/* Works in GCC as an extension: https://stackoverflow.com/a/3692486/895245 */
/*int i;*/
/* OK: extern. Will use the one in main. */
extern int i;
/* OK: only visible to this file. */
static int si = 0;
void a() {
i++;
si++;
puts("a()");
printf("i = %d\n", i);
printf("si = %d\n", si);
puts("");
}
main.c
#include <stdio.h>
int i = 0;
static int si = 0;
void a();
void m() {
i++;
si++;
puts("m()");
printf("i = %d\n", i);
printf("si = %d\n", si);
puts("");
}
int main() {
m();
m();
a();
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
Выход:
m()
i = 1
si = 1
m()
i = 2
si = 2
a()
i = 3
si = 1
a()
i = 4
si = 2
Устный
- есть две отдельные переменные для
si
, по одной для каждого файла
- существует одна общая переменная для
i
Как обычно, чем меньше область действия, тем лучше, поэтому всегда объявляйте переменные static
, если можете.
В программировании на C файлы часто используются для представления «классов», а static
переменные представляют частные статические члены класса.
Что говорят об этом стандарты
C99 N1256 draft 6.7.1 «Спецификаторы класса хранения» говорят, что static
является «спецификатором класса хранения».
6.2.2 / 3 «Связи идентификаторов» говорит static
подразумевает internal linkage
:
Если объявление идентификатора области файла для объекта или функции содержит статический спецификатор класса хранения, идентификатор имеет внутреннюю связь.
и 6.2.2 / 2 говорят, что internal linkage
ведет себя так, как в нашем примере:
В наборе единиц перевода и библиотек, составляющих целую программу, каждое объявление определенного идентификатора с внешней связью обозначает один и тот же объект или функцию. В пределах одной единицы перевода каждое объявление идентификатора с внутренней связью обозначает один и тот же объект или функцию.
где "единица перевода - это исходный файл после предварительной обработки.
Как GCC реализует это для ELF (Linux)?
С привязкой STB_LOCAL
.
Если мы скомпилируем:
int i = 0;
static int si = 0;
и разберите таблицу символов с помощью:
readelf -s main.o
вывод содержит:
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000004 4 OBJECT LOCAL DEFAULT 4 si
10: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 i
поэтому связывание - единственное существенное различие между ними. 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
, символ si
полностью удаляется из таблицы символов: его нельзя использовать извне. TODO зачем вообще хранить статические переменные в таблице символов, когда нет оптимизации? Могут ли они быть использованы для чего-либо? Возможно для отладки.
См. Также
C ++ анонимные пространства имен
В C ++ вы можете использовать анонимные пространства имен вместо статических, что обеспечивает аналогичный эффект, но дополнительно скрывает определения типов: Безымянные / анонимные пространства имен и статические функции