Приклад мінімальної кількості файлів, що можна виконати
Тут я проілюструю, як 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
функції представляють "приватні" методи класу.
Поширений шаблон С - це пройти а this
структуру навколо як перший аргумент "методу", який в основному те, що C ++ робить під кришкою.
Які стандарти про це говорять
У проекті 6.7.1 " Специфікатори класу зберігання" C99 N1256 говориться, що 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 ++ ви можете використовувати анонімні простори імен замість статичних, що досягає аналогічного ефекту, але додатково приховує визначення типів: Без назви / анонімні простори імен проти статичних функцій