Exemplo de escopo mínimo de vários arquivos executáveis
Aqui ilustro como staticafeta 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, staticse puder.
Na programação C, os arquivos são frequentemente usados para representar "classes" e as staticfunções representam métodos "particulares" da classe.
Um padrão C comum é passar uma thisestrutura 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 staticimplica 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 linkagese 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_LOCALligaçã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 .bssseção, portanto esperamos que seja diferente.
STB_LOCALestá 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_GLOBALe 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 sfsí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