O que é uma função "estática" em C?

506

A pergunta era sobre simples funções, não static métodos, conforme esclarecido nos comentários.

Eu entendo o que é uma staticvariável, mas o que é uma staticfunção?

E por que se eu declarar uma função, digamos void print_matrix, em digamos a.c(SEM a.h) e inclua "a.c"- eu recebo "print_matrix@@....) already defined in a.obj", MAS se eu a declarar como static void print_matrixela compila?

ATUALIZAÇÃO Apenas para esclarecer as coisas - sei que incluir .cé ruim, como muitos de vocês apontaram. Eu apenas faço isso para liberar espaço temporariamente main.caté ter uma idéia melhor de como agrupar todas essas funções em arquivos .he .carquivos adequados . Apenas uma solução rápida e temporária.

Slava V
fonte

Respostas:

685

staticfunções são funções que são visíveis apenas para outras funções no mesmo arquivo (mais precisamente a mesma unidade de conversão ).

Edição : Para aqueles que pensavam, que o autor das perguntas significava um 'método de classe': Como a pergunta é marcada, Cele significa uma função C simples e antiga. Para métodos de classe (C ++ / Java / ...), staticsignifica que esse método pode ser chamado na própria classe, nenhuma instância dessa classe é necessária.

Johannes Weiss
fonte
2
Na verdade, eu não o marquei como c ++, alguns administradores provavelmente o fizeram, mas era sobre C ++, então qual é a diferença em C ++?
Slava V
16
Os métodos C ++ geralmente são chamados de "funções-membro", então eu concordo que o C ++ introduz um pouco de ambiguidade. A culpa não é sua - o idioma usa apenas a palavra-chave para duas coisas diferentes.
187 Chuck
2
Não, ele ainda significa uma função C ++. Uma função livre de C ++ em vez de uma função de membro C ++.
Lightness Races em órbita
3
@ Chuck: a terminologia C ++ nunca usa a palavra "método"; essa é a terminologia Java - nos documentos padrão do C ++, sempre é chamada de "função membro" (consulte esta resposta ou este glossário de termos C ++ versus Java (por exemplo, o C ++ usa "membro de dados" e Java usa "campo", etc)).
ShreevatsaR
6
Para esclarecer um pouco esta resposta: o nome da função é visível apenas para outras partes da mesma unidade de tradução, abaixo da primeira declaração desse nome. A função pode ser chamada de outras unidades (e partes anteriores da mesma unidade) por outros meios, por exemplo, um ponteiro de função.
MM
199

Há uma grande diferença entre funções estáticas em C e funções membro estáticas em C ++. Em C, uma função estática não é visível fora de sua unidade de conversão, que é o arquivo de objeto em que é compilado. Em outras palavras, tornar uma função estática limita seu escopo. Você pode pensar em uma função estática como "privada" para seu arquivo * .c (embora isso não esteja estritamente correto).

No C ++, "estático" também pode ser aplicado a funções de membros e membros de dados de classes. Um membro de dados estáticos também é chamado de "variável de classe", enquanto um membro de dados não estáticos é uma "variável de instância". Esta é a terminologia Smalltalk. Isso significa que há apenas uma cópia de um membro de dados estáticos compartilhada por todos os objetos de uma classe, enquanto cada objeto tem sua própria cópia de um membro de dados não estáticos. Portanto, um membro de dados estático é essencialmente uma variável global, que é membro de uma classe.

As funções de membro não estático podem acessar todos os membros de dados da classe: estático e não estático. As funções de membro estático podem operar apenas nos membros de dados estáticos.

Uma maneira de pensar sobre isso é que, no C ++, os membros de dados estáticos e as funções de membros estáticos não pertencem a nenhum objeto, mas a toda a classe.

Dima
fonte
42
O C ++ também possui arquivos estáticos. Não há necessidade de trazer C para isso.
Lightness Races em órbita em
17
No C ++, uma função estática é uma função estática. Uma função membro estática é uma função membro estática, também conhecida como método. O fato de C não ter membros não significa que as funções sejam "C".
Gerasimos R
3
existe alguma diferença entre var global e var estático de classe (exceto namespace)?
Alexander Malakhov
3
O espaço para nome é a principal diferença. A outra diferença é que você pode tornar um membro de dados estático privado e, portanto, acessível apenas de dentro das funções de membro da classe. Em outras palavras, você tem muito mais controle sobre um membro de dados estático em comparação com uma variável global.
Dima
2
Alguém poderia explicar por que pensar em uma função estática como privada para seu arquivo .c não é estritamente correto? O que resta a dizer?
YoTengoUnLCD
78

Existem dois usos para a palavra-chave static quando se trata de funções em C ++.

O primeiro é marcar a função como tendo ligação interna, para que não possa ser referenciada em outras unidades de conversão. Esse uso foi descontinuado em C ++. Os namespaces sem nome são preferidos para esse uso.

// inside some .cpp file:

static void foo();    // old "C" way of having internal linkage

// C++ way:
namespace
{
   void this_function_has_internal_linkage()
   {
      // ...
   }
}

O segundo uso está no contexto de uma classe. Se uma classe tem uma função de membro estática, isso significa que a função é um membro da classe (e tem o acesso usual a outros membros), mas não precisa ser invocada por meio de um objeto específico. Em outras palavras, dentro dessa função, não há ponteiro "este".

Brian Neal
fonte
1
A questão é sobre estática em c.
Deqing
8
@Deqing a pergunta foi originalmente marcada com C ++ e o autor fala sobre o uso de arquivos ".cpp".
27613 Brian Neal
57

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

Ciro Santilli adicionou uma nova foto
fonte
3
nota: void f() { puts("sf"); }(ou seja, duas definições de f()) causa comportamento indefinido sem necessidade de diagnóstico. É um problema de qualidade do vinculador ver realmente uma mensagem de erro.
MM
2
Esta é a explicação melhor e precisa! Do que você!
do Aqua
20

A seguir, são apresentadas as funções simples de C - em uma classe C ++, o modificador 'estático' tem outro significado.

Se você tiver apenas um arquivo, esse modificador não fará absolutamente nenhuma diferença. A diferença está em projetos maiores com vários arquivos:

Em C, todo "módulo" (uma combinação de sample.c e sample.h) é compilado independentemente e, posteriormente, todos os arquivos de objetos compilados (sample.o) são vinculados a um arquivo executável pelo vinculador.

Digamos que você tenha vários arquivos incluídos no arquivo principal e dois deles tenham uma função usada apenas internamente por conveniência chamada add(int a, b)- o compilador criaria facilmente arquivos de objetos para esses dois módulos, mas o vinculador gerará um erro, porque ele encontra duas funções com o mesmo nome e não sabe qual delas deve usar (mesmo que não haja nada para vincular, porque elas não são usadas em outro lugar, mas em seu próprio arquivo).

É por isso que você torna essa função, que é usada apenas interna, uma função estática. Nesse caso, o compilador não cria a bandeira típica "você pode vincular essa coisa" para o vinculador, para que o vinculador não veja essa função e não gere um erro.

dersimn
fonte
16

Primeiro: geralmente é uma má idéia incluir um .cpparquivo em outro arquivo - isso gera problemas como este :-) A maneira normal é criar unidades de compilação separadas e adicionar um arquivo de cabeçalho para o arquivo incluído.

Em segundo lugar:

C ++ tem alguma terminologia confusa aqui - eu não sabia sobre isso até apontado nos comentários.

a) static functions- herdado de C e do que você está falando aqui. Fora de qualquer classe. Uma função estática significa que ela não é visível fora da unidade de compilação atual - portanto, no seu caso, o a.obj possui uma cópia e o outro código possui uma cópia independente. (Inchando o executável final com várias cópias do código).

b) static member function- o que Orientação a Objetos chama de método estático . Vive dentro de uma classe. Você chama isso com a classe e não através de uma instância de objeto.

Essas duas definições diferentes de funções estáticas são completamente diferentes. Tenha cuidado - aqui estejam dragões.

Douglas Leeder
fonte
Bem, eu faço isso apenas para limpar algum espaço TEMPORARIAMENTE no main.cpp até decidir como organizar o arquivo em bibliotecas junto com os .hpp apropriados. Existe uma idéia melhor de como fazer isso?
Slava V
1
A terminologia correta no C ++ é função de membro, não método. Não existem "métodos" no C ++ legalese. Método é um termo geral de OO. O C ++ os implementa por meio de funções membro.
18119 Brian Neal
14

definições de função estática marcarão esse símbolo como interno. Portanto, não será visível para vinculação externa, mas apenas para funções na mesma unidade de compilação, geralmente o mesmo arquivo.

raimue
fonte
7

Uma função estática é aquela que pode ser chamada na própria classe, em oposição a uma instância da classe.

Por exemplo, um não estático seria:

Person* tom = new Person();
tom->setName("Tom");

Este método funciona em uma instância da classe, não na própria classe. No entanto, você pode ter um método estático que pode funcionar sem ter uma instância. Às vezes, isso é usado no padrão Factory:

Person* tom = Person::createNewPerson();
Papagaios
fonte
2
Parece-me que você está falando sobre "método" estático, não "função"?
Slava V
Presumi que você estivesse se referindo a funções estáticas dentro de uma classe.
21410 Papagaios
Se eu soubesse que "métodos" são chamados de "funções de método" em C ++, ficaria mais claro sobre isso. Bem, agora eu faço :) Obrigado de qualquer maneira
Slava V
5
Não existem "métodos" em C ++, apenas funções. O padrão C ++ nunca menciona "métodos", apenas "funções".
1811 Brian Neal
1
@ Poça, eu sei o que você está dizendo, mas no padrão C ++ não há definição de um "método". C ++ possui apenas funções, de vários tipos. "Método" é um termo geral de OO e é usado em outros idiomas e informalmente em C ++. Um método é formalmente conhecido como uma "função de membro" em C ++.
Brian Neal
7

Nitidez menor: funções estáticas são visíveis para uma unidade de conversão, que na maioria dos casos práticos é o arquivo no qual a função está definida. O erro que você está recebendo é geralmente chamado de violação da regra de definição única.

O padrão provavelmente diz algo como:

"Todo programa deve conter exatamente uma definição de cada função ou objeto não-linear usado nesse programa; não é necessário diagnóstico".

Essa é a maneira C de observar as funções estáticas. No entanto, isso foi preterido em C ++.

Além disso, em C ++, você pode declarar estáticas as funções de membro. Essas são principalmente meta-funções, ou seja, não descrevem / modificam o comportamento / estado de um objeto em particular, mas agem em toda a classe em si. Além disso, isso significa que você não precisa criar um objeto para chamar uma função de membro estático. Além disso, isso também significa que você obtém acesso apenas a variáveis ​​de membro estáticas de dentro dessa função.

Eu adicionaria ao exemplo de Parrot o padrão Singleton, que é baseado nesse tipo de função de membro estático para obter / usar um único objeto durante toda a vida útil de um programa.

dirkgently
fonte
7

A resposta para a função estática depende do idioma:

1) Em idiomas sem OOPS como C, significa que a função está acessível apenas dentro do arquivo onde está definido.

2) Em linguagens com OOPS como C ++, significa que a função pode ser chamada diretamente na classe sem criar uma instância dela.

user2410022
fonte
Isso não é verdade. A explicação do seu segundo parágrafo refere-se a " funções-membro estáticas " de uma classe, não a " funções estáticas ". Em C ++, uma função qualificada também statictem escopo de arquivo, como em C.
RobertS suporta Monica Cellio
0

Como a função estática é visível apenas neste arquivo. Na verdade, o compilador pode fazer alguma otimização para você se você declarar "estático" para alguma função.

Aqui está um exemplo simples.

main.c

#include <stdio.h>

static void test() 
{
    ghost(); // This is an unexist function.
}

int main()
{
    int ret = 0;

#ifdef TEST
#else
    test();
#endif
    return (ret);
} 

E compile com

gcc -o main main.c

Você verá que falhou. Porque você nem implementa a função ghost ().

Mas e se usarmos o seguinte comando.

gcc -DTEST -O2 -o main main.c

É um sucesso , e este programa pode ser executado normalmente.

Por quê? Existem 3 pontos-chave.

  1. -O2: nível de otimização do compilador pelo menos 2.
  2. -DTEST: Defina TEST, portanto test () não será chamado.
  3. Definido "estático" para test ().

Somente se essas três condições forem verdadeiras, você poderá passar na compilação. Por causa dessa declaração "estática", o compilador pode confirmar que test () NUNCA será chamado em outro arquivo. Seu compilador pode remover test () ao compilar. Como não precisamos de test (), não importa se ghost () está definido ou implementado.

PoJyun Chiou
fonte
0

" O que é uma staticfunção " "em C? "

Vamos começar no início.

É tudo baseado em uma coisa chamada "ligação":

" Um identificador declarado em escopos diferentes ou no mesmo escopo mais de uma vez pode ser feito para se referir ao mesmo objeto ou função por um processo chamado vínculo. 29) Existem três tipos de vínculo: externo, interno e nenhum. "

Fonte: C18, 6.2.2 / 1


"No conjunto de unidades de tradução e bibliotecas que constituem um programa inteiro, cada declaração de um identificador específico com vínculo externo indica o mesmo objeto ou função. Dentro de uma unidade de tradução, cada declaração de um identificador com vínculo interno indica o mesmo objeto ou função Cada declaração de um identificador sem vínculo indica uma entidade única. "

Fonte: C18, 6.2.2 / 2


Se uma função for definida sem um especificador de classe de armazenamento, a função externterá um vínculo por padrão:

"Se a declaração de um identificador para uma função não tiver um especificador de classe de armazenamento, sua ligação será determinada exatamente como se tivesse sido declarada com o especificador de classe de armazenamento externo ".

Fonte: C18, 6.2.2 / 5

Isso significa que - se o seu programa contém várias unidades de tradução / arquivos de origem ( .cou .cpp) - a função é visível em todas as unidades de tradução / arquivos de origem que o seu programa possui.

Isso pode ser um problema em alguns casos. E se você quiser usar duas funções diferentes (definições), mas com o mesmo nome de função em dois contextos diferentes (na verdade, o contexto do arquivo).

Em C e C ++, o staticqualificador da classe de armazenamento aplicado a uma função no escopo do arquivo (não uma função membro estática de uma classe em C ++ ou uma função em outro bloco) agora ajuda e significa que a respectiva função só é visível dentro de a unidade de tradução / arquivo de origem em que foi definido e não nos outros TLUs / arquivos.

"Se a declaração de um identificador de escopo de arquivo para um objeto ou uma função contiver o especificador de classe de armazenamento estático , o identificador terá ligação interna. 30)"


30) Uma declaração de função pode conter a classe de armazenamento específica apenas se estiver no escopo do arquivo; ver 6.7.1.

Fonte: C18, 6.2.2 / 3


Assim, uma staticfunção só faz sentido, se:

  1. Seu programa está contido em várias unidades de tradução / arquivos de origem ( .cou .cpp).

    e

  2. Você deseja limitar o escopo de uma função ao arquivo, no qual a função específica está definida.

Se esses dois requisitos não corresponderem, você não precisa se preocupar em qualificar uma função como static.


Notas laterais:

RobertS suporta Monica Cellio
fonte