Exemplo de escopo mínimo de vários arquivos executáveis
Aqui ilustro como static
afeta o escopo das definições de função em vários arquivos.
ac
#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 upstream .
Compile e execute:
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main
Resultado:
main f
main sf
main f
a sf
Interpretação
- existem duas funções separadas
sf
, uma para cada arquivo
- existe uma única função compartilhada
f
Como de costume, quanto menor o escopo, melhor, sempre declare funções, static
se puder.
Na programação C, os arquivos são frequentemente usados para representar "classes" e as static
funções representam métodos "particulares" da classe.
Um padrão C comum é passar uma this
estrutura como o primeiro argumento do "método", que é basicamente o que o C ++ faz sob o capô.
O que os padrões dizem sobre isso
C99 N1256 draft 6.7.1 "Especificadores da classe de armazenamento" diz que static
é um "especificador da classe de armazenamento".
6.2.2 / 3 "Ligações de identificadores" diz static
implica internal linkage
:
Se a declaração de um identificador de escopo de arquivo para um objeto ou função contiver o especificador de classe de armazenamento estático, o identificador terá ligação interna.
e 6.2.2 / 2 diz que internal linkage
se comporta como no nosso exemplo:
No conjunto de unidades de tradução e bibliotecas que constituem um programa inteiro, cada declaração de um identificador específico com ligação externa indica o mesmo objeto ou função. Dentro de uma unidade de conversão, cada declaração de um identificador com ligação interna indica o mesmo objeto ou função.
onde "unidade de tradução" é um arquivo de origem após o pré-processamento.
Como o GCC o implementa para o ELF (Linux)?
Com a STB_LOCAL
ligação.
Se compilarmos:
int f() { return 0; }
static int sf() { return 0; }
e desmonte a tabela de símbolos com:
readelf -s main.o
a saída contém:
Num: Value Size Type Bind Vis Ndx Name
5: 000000000000000b 11 FUNC LOCAL DEFAULT 1 sf
9: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 f
portanto, a ligação é a única diferença significativa entre eles. Value
é apenas o deslocamento para a .bss
seção, portanto esperamos que seja diferente.
STB_LOCAL
está documentado na especificação ELF em http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :
STB_LOCAL Os símbolos locais não são visíveis fora do arquivo de objeto que contém sua definição. Símbolos locais com o mesmo nome podem existir em vários arquivos sem interferir entre si
o que a torna uma escolha perfeita para representar static
.
As funções sem estática são STB_GLOBAL
e a especificação diz:
Quando o editor de links combina vários arquivos de objetos realocáveis, ele não permite várias definições de símbolos STB_GLOBAL com o mesmo nome.
que é coerente com os erros de link em várias definições não estáticas.
Se acionarmos a otimização -O3
, o sf
símbolo será removido inteiramente da tabela de símbolos: ele não pode ser usado de fora de qualquer maneira. TODO por que manter funções estáticas na tabela de símbolos quando não há otimização? Eles podem ser usados para qualquer coisa?
Veja também
Namespace anônimos em C ++
No C ++, convém usar espaços para nome anônimos em vez de estático, o que gera um efeito semelhante, mas oculta ainda mais as definições de tipo: espaços para nome anônimos / anônimos / funções estáticas