função estática em C

172

Qual é o sentido de tornar uma função estática em C?

Cenoc
fonte
7
@ nightcracker: Não existem coisas como "métodos" em C ++. Eu acho que você está confuso com o Objective-C.
Bo Persson
1
Não, eu estou confuso com Python. Uma função dentro de uma classe é chamada de método em Python.
orlp
6
possível duplicata de O que é uma função "estática"? (em C)
atoMerz

Respostas:

212

Criar uma função a staticoculta de outras unidades de tradução, o que ajuda a fornecer o encapsulamento .

helper_file.c

int f1(int);        /* prototype */
static int f2(int); /* prototype */

int f1(int foo) {
    return f2(foo); /* ok, f2 is in the same translation unit */
                    /* (basically same .c file) as f1         */
}

int f2(int foo) {
    return 42 + foo;
}

main.c :

int f1(int); /* prototype */
int f2(int); /* prototype */

int main(void) {
    f1(10); /* ok, f1 is visible to the linker */
    f2(12); /* nope, f2 is not visible to the linker */
    return 0;
}
pmg
fonte
8
A unidade de tradução é a terminologia correta a ser usada aqui? O arquivo de objeto não seria mais preciso? Pelo que entendi, uma função estática está oculta do vinculador e o vinculador não opera em unidades de conversão.
Steven Eckhoff
2
Eu também deveria ter dito que gosto de pensar nisso como oculto no vinculador; parece mais claro assim.
Steven Eckhoff
1
então, função interna (que com certeza não a chamamos fora de seu arquivo c), devemos colocá-la como função estática, certo? Portanto, podemos ter certeza de que não pode ligar para outro lugar. Obrigado :)
hqt
1
Como você compila isso? Você usa #include <helper_file.c>? Eu acho que isso tornaria uma única unidade de tradução então ...
Atcold
2
@Atcold: da maneira que escrevi o código, você simplesmente inclui os 2 arquivos de origem na linha de comando, assim gcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.c. Os protótipos para as funções estão presentes nos dois arquivos de origem (não há necessidade de arquivos de cabeçalho). O vinculador resolverá as funções.
pmg
80

pmg é direto sobre encapsulamento; além de ocultar a função de outras unidades de conversão (ou melhor, por causa disso), as funções de criação statictambém podem conferir benefícios de desempenho na presença de otimizações do compilador.

Como uma staticfunção não pode ser chamada de qualquer lugar fora da unidade de tradução atual (a menos que o código leve um ponteiro para seu endereço), o compilador controla todos os pontos de chamada nela.

Isso significa que é livre usar uma ABI fora do padrão, incorporá-la inteiramente ou executar qualquer número de outras otimizações que talvez não sejam possíveis para uma função com ligação externa.

Stephen Canon
fonte
9
... a menos que o endereço da função seja usado.
caf
1
@caf O que você quer dizer com endereço da função? Para mim, a noção de funções / variáveis ​​com endereços ou sendo atribuídas a endereços em tempo de compilação é um pouco confusa. Você pode por favor elaborar?
SayeedHussain
2
@ codificador criptográfico: Seu programa está carregado na memória, portanto, as funções também têm um local de memória e o endereço pode ser obtido. Com um ponteiro de função, você pode chamar qualquer um desses. Se você fizer isso, reduz a lista de otimizações que o compilador pode executar, pois o código deve permanecer intacto no mesmo local.
5
@ codypticcoder: quero dizer que uma expressão avalia um ponteiro para a função e faz algo com ela que não seja chamar imediatamente a função. Se um ponteiro para uma staticfunção escapar da unidade de tradução atual, essa função poderá ser chamada diretamente de outras unidades de tradução.
Caf
@caf se o endereço da função for escolhido, o compilador detectaria isso e desativaria as otimizações de função estática mencionadas nesta resposta (por exemplo, usando uma ABI não padrão)? Suponho que teria que ser.
sevko 12/07/19
28

A staticpalavra-chave em C é usada em um arquivo compilado (.c em oposição a .h) para que a função exista apenas nesse arquivo.

Normalmente, quando você cria uma função, o compilador gera um cruft que o vinculador pode usar para, bem, vincular uma chamada de função a essa função. Se você usar a palavra-chave estática, outras funções no mesmo arquivo poderão chamar essa função (porque isso pode ser feito sem o recurso ao vinculador), enquanto o vinculador não possui informações que permitam que outros arquivos acessem a função.

3Doubloons
fonte
1
3Doub: o uso da palavra "cruft" é mais preciso do que você acredita. No contexto da pergunta, "cruft" é a palavra certa a ser usada aqui.
Erik Aronesty
@ 3Doubloons Concordo que é simplificado, mas acho que facilita muito o entendimento para iniciantes.
Ingo Bürk
11

Olhando para as postagens acima, gostaria de apontar um detalhe.

Suponha que nosso arquivo principal ("main.c") seja assim:

#include "header.h"

int main(void) {
    FunctionInHeader();
}

Agora considere três casos:

  • Caso 1: Nosso arquivo de cabeçalho ("header.h") fica assim:

    #include <stdio.h>
    
    static void FunctionInHeader();
    
    void FunctionInHeader() {
        printf("Calling function inside header\n");
    }

    Em seguida, o seguinte comando no linux:

    gcc main.c header.h -o main

    terá sucesso ! Depois disso, se alguém executar

    ./main

    A saída será

    Chamando a função dentro do cabeçalho

    Qual é o que essa função estática deve imprimir.

  • Caso 2: Nosso arquivo de cabeçalho ("header.h") se parece com o seguinte:

    static void FunctionInHeader();     

    e também temos mais um arquivo "header.c", que se parece com isso:

    #include <stdio.h>
    
    #include "header.h"
    
    void FunctionInHeader() {
        printf("Calling function inside header\n");
    }

    Em seguida, o seguinte comando

    gcc main.c header.h header.c -o main

    dará um erro.

  • Caso 3:

    Semelhante ao caso 2, exceto que agora nosso arquivo de cabeçalho ("header.h") é:

    void FunctionInHeader(); // keyword static removed

    Então, o mesmo comando do caso 2 será bem-sucedido e a execução de ./main continuará fornecendo o resultado esperado.

Portanto, a partir desses testes (executados na máquina Acer x86, Ubuntu OS), assumi que

A palavra-chave estática impede que a função seja chamada em outro arquivo * .c que não seja onde está definido.

Corrija-me se eu estiver errada.

mercury0114
fonte
5

Programadores C usam o atributo static para ocultar declarações de variáveis ​​e funções dentro de módulos, da mesma forma que você usaria declarações públicas e privadas em Java e C ++. Os arquivos de origem C desempenham o papel de módulos. Qualquer variável global ou função declarada com o atributo estático é privada para esse módulo. Da mesma forma, qualquer variável global ou função declarada sem o atributo estático é pública e pode ser acessada por qualquer outro módulo. É uma boa prática de programação proteger suas variáveis ​​e funções com o atributo estático sempre que possível.

Sistemas de computador
fonte
4

A resposta da pmg é muito convincente. Se você deseja saber como as declarações estáticas funcionam no nível do objeto, as informações abaixo podem ser interessantes para você. Reutilizei o mesmo programa escrito por pmg e o compilei em um arquivo .so (objeto compartilhado)

O conteúdo a seguir é depois de despejar o arquivo .so em algo legível por humanos

0000000000000675 f1 : endereço da função f1

000000000000068c f2 : endereço da função f2 (staticc)

observe a diferença no endereço da função, isso significa alguma coisa. Para uma função declarada com endereço diferente, pode muito bem significar que f2 mora muito longe ou em um segmento diferente do arquivo de objeto.

Os vinculadores usam algo chamado PLT (tabela de vinculação de procedimentos) e GOT (tabela de compensações globais) para entender os símbolos aos quais eles têm acesso.

Por enquanto, pense que GOT e PLT vinculam magicamente todos os endereços e uma seção dinâmica contém informações de todas essas funções que são visíveis pelo vinculador.

Depois de despejar a seção dinâmica do arquivo .so, obtemos várias entradas, mas apenas interessadas na função f1 e f2 .

A seção dinâmica mantém a entrada apenas para a função f1 no endereço 0000000000000675 e não para f2 !

Num: Valor Tamanho Tipo Vinculação Vis Ndx Name

 9: 0000000000000675    23 FUNC    GLOBAL DEFAULT   11 f1

E é isso !. A partir disso, fica claro que o vinculador não conseguiu encontrar a função f2 , pois não está na seção dinâmica do arquivo .so.

human.js
fonte
0

Quando for necessário restringir o acesso a algumas funções, usaremos a palavra-chave estática ao definir e declarar uma função.

            /* file ab.c */ 
static void function1(void) 
{ 
  puts("function1 called"); 
} 
And store the following code in another file ab1.c

/* file ab1.c  */ 
int main(void) 
{ 
 function1();  
  getchar(); 
  return 0;   
} 
/* in this code, we'll get a "Undefined reference to function1".Because function 1 is declared static in file ab.c and can't be used in ab1.c */
Amna Salman
fonte
Esta resposta não é muito útil.
fiscblog