Che cos'è una funzione "statica" in C?


506

La domanda era chiara funzioni, no static metodi, come chiarito nei commenti.

Capisco cos'è una staticvariabile, ma cos'è una staticfunzione?

E perché se dichiaro una funzione, diciamo void print_matrix, diciamo a.c(SENZA a.h) e includo "a.c"- ottengo "print_matrix@@....) already defined in a.obj", MA se lo dichiaro come static void print_matrixallora si compila?

AGGIORNAMENTO Solo per chiarire le cose - so che includere .cè un male, come molti di voi hanno sottolineato. Lo faccio solo per liberare temporaneamente lo spazio main.cfinché non ho un'idea migliore di come raggruppare tutte quelle funzioni in file .he .cfile propri . Solo una soluzione temporanea e veloce.

Risposte:


685

staticLe funzioni sono funzioni visibili solo ad altre funzioni nello stesso file (più precisamente la stessa unità di traduzione ).

EDIT : Per coloro che pensavano che l'autore delle domande significasse un "metodo di classe": quando la domanda è taggata C, significa una semplice funzione C. Per i metodi di classe (C ++ / Java / ...), staticsignifica che questo metodo può essere chiamato sulla classe stessa, non è necessaria alcuna istanza di quella classe.


2
In realtà non l'ho taggato in c ++, probabilmente alcuni amministratori lo hanno fatto, ma si trattava di C ++, quindi qual è la differenza in C ++?
Slava V,

16
I metodi C ++ vengono spesso definiti "funzioni membro", quindi concordo sul fatto che il C ++ introduce un po 'di ambiguità. Non è colpa tua: il linguaggio usa solo la parola chiave per due cose diverse.
Chuck,

2
No, intende ancora una funzione C ++. Una funzione libera C ++ anziché una funzione membro C ++.
Razze di leggerezza in orbita

3
@Chuck: la terminologia C ++ non usa mai la parola "metodo"; questa è la terminologia Java - nei documenti standard C ++ viene sempre chiamata "funzione membro" (vedere questa risposta o questo glossario di termini C ++ vs Java (ad es. C ++ utilizza "membro dati" e Java utilizza "campo", ecc.)).
ShreevatsaR,

6
Per chiarire un po 'questa risposta: il nome della funzione è visibile solo ad altre parti della stessa unità di traduzione, sotto la prima dichiarazione di quel nome. La funzione può essere chiamata da altre unità (e parti precedenti della stessa unità) tramite altri mezzi, ad esempio un puntatore a funzione.
MM

199

C'è una grande differenza tra le funzioni statiche in C e le funzioni dei membri statici in C ++. In C, una funzione statica non è visibile al di fuori della sua unità di traduzione, che è il file oggetto in cui è compilata. In altre parole, rendere statica una funzione ne limita l'ambito. Puoi pensare a una funzione statica come "privata" al suo file * .c (anche se non è strettamente corretto).

In C ++, "statico" può essere applicato anche alle funzioni membro e ai membri dei dati delle classi. Un membro di dati statici è anche chiamato "variabile di classe", mentre un membro di dati non statici è una "variabile di istanza". Questa è la terminologia di Smalltalk. Ciò significa che esiste una sola copia di un membro di dati statici condivisa da tutti gli oggetti di una classe, mentre ogni oggetto ha la propria copia di un membro di dati non statici. Quindi un membro di dati statici è essenzialmente una variabile globale, ovvero un membro di una classe.

Le funzioni dei membri non statici possono accedere a tutti i membri dei dati della classe: statici e non statici. Le funzioni dei membri statici possono operare solo sui membri dei dati statici.

Un modo di pensare a questo è che in C ++ i membri di dati statici e le funzioni di membri statici non appartengono a nessun oggetto, ma all'intera classe.


42
Anche C ++ ha un file statico. Non c'è bisogno di portare C in questo.
Razze di leggerezza in orbita

17
In C ++, una funzione statica è una funzione statica. Una funzione membro statica è una funzione membro statica, nota anche come metodo. Il fatto che C non abbia membri non significa che le funzioni siano "C".
Gerasimos R,

3
c'è qualche differenza tra var globale e var statico di classe (tranne lo spazio dei nomi)?
Alexander Malakhov,

3
Lo spazio dei nomi è la differenza principale. L'altra differenza è che puoi rendere un membro di dati statici privato e quindi accessibile solo dalle funzioni membro della classe. In altre parole, hai un controllo molto maggiore su un membro di dati statici rispetto a una variabile globale.
Dima,

2
Qualcuno potrebbe spiegare perché pensare che una funzione statica come privata nel suo file .c non sia strettamente corretta? Cosa resta da dire?
YoTengoUnLCD,

78

Esistono due usi per la parola chiave static quando si tratta di funzioni in C ++.

Il primo è contrassegnare la funzione come con collegamento interno in modo che non possa essere referenziata in altre unità di traduzione. Questo utilizzo è deprecato in C ++. Gli spazi dei nomi senza nome sono preferiti per questo utilizzo.

// inside some .cpp file:

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

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

Il secondo utilizzo è nel contesto di una classe. Se una classe ha una funzione membro statica, ciò significa che la funzione è un membro della classe (e ha il solito accesso ad altri membri), ma non è necessario invocarla attraverso un oggetto particolare. In altre parole, all'interno di quella funzione, non esiste un puntatore "questo".


1
La domanda è statica in c.
Deqing,

8
@Deqing la domanda era originariamente taggata in C ++ e l'autore parla dell'uso dei file ".cpp".
Brian Neal,

57

Esempio di ambito multi-file eseguibile minimo

Qui illustrerò come staticinfluenza l'ambito delle definizioni delle funzioni su più file.

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 a monte .

Compila ed esegui:

gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main

Produzione:

main f
main sf
main f
a sf

Interpretazione

  • ci sono due funzioni separate sf, una per ogni file
  • c'è una singola funzione condivisa f

Come al solito, minore è l'ambito, meglio è, quindi staticpuoi sempre dichiarare le funzioni se puoi.

Nella programmazione C, i file vengono spesso utilizzati per rappresentare "classi" e le staticfunzioni rappresentano metodi "privati" della classe.

Un modello C comune è quello di passare una thisstruttura come primo argomento "metodo", che è fondamentalmente ciò che fa C ++ sotto il cofano.

Cosa dicono gli standard al riguardo

Bozza C99 N1256 6.7.1 " Identificatori della classe di archiviazione" indica che si statictratta di un " identificatore della classe di archiviazione".

6.2.2 / 3 "Collegamenti di identificatori" dice staticimplica internal linkage:

Se la dichiarazione di un identificatore di ambito di file per un oggetto o una funzione contiene l'identificatore di classe di archiviazione statico, l'identificatore ha un collegamento interno.

e 6.2.2 / 2 dice che internal linkagesi comporta come nel nostro esempio:

Nell'insieme di unità di traduzione e librerie che costituisce un intero programma, ogni dichiarazione di un particolare identificatore con collegamento esterno indica lo stesso oggetto o funzione. All'interno di un'unità di traduzione, ogni dichiarazione di un identificatore con collegamento interno indica lo stesso oggetto o funzione.

dove "unità di traduzione" è un file sorgente dopo la preelaborazione.

In che modo GCC lo implementa per ELF (Linux)?

Con la STB_LOCALrilegatura.

Se compiliamo:

int f() { return 0; }
static int sf() { return 0; }

e disassemblare la tabella dei simboli con:

readelf -s main.o

l'output contiene:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  5: 000000000000000b    11 FUNC    LOCAL  DEFAULT    1 sf
  9: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 f

quindi l'associazione è l'unica differenza significativa tra di loro. Valueè solo il loro offset nella .bsssezione, quindi ci aspettiamo che differisca.

STB_LOCALè documentato sulle specifiche ELF all'indirizzo http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :

STB_LOCAL I simboli locali non sono visibili all'esterno del file oggetto che contiene la loro definizione. Simboli locali con lo stesso nome possono esistere in più file senza interferire tra loro

che lo rende una scelta perfetta da rappresentare static.

Le funzioni senza elettricità statica sono STB_GLOBALe la specifica dice:

Quando l'editor di collegamenti combina diversi file di oggetti trasferibili, non consente più definizioni di simboli STB_GLOBAL con lo stesso nome.

che è coerente con gli errori di collegamento su più definizioni non statiche.

Se ottimizziamo l'ottimizzazione con -O3, il sfsimbolo viene rimosso completamente dalla tabella dei simboli: non può comunque essere utilizzato dall'esterno. TODO perché mantenere le funzioni statiche sulla tabella dei simboli quando non c'è ottimizzazione? Possono essere usati per qualsiasi cosa?

Guarda anche

Spazi dei nomi anonimi C ++

In C ++, potresti voler usare spazi dei nomi anonimi anziché statici, il che ottiene un effetto simile, ma nasconde ulteriormente le definizioni dei tipi: spazi dei nomi senza nome / anonimi rispetto alle funzioni statiche


3
nota: void f() { puts("sf"); }(ovvero due definizioni di f()) provoca un comportamento indefinito senza necessità di diagnostica. È un problema di qualità del linker vedere effettivamente un messaggio di errore.
MM

2
Questa è la spiegazione migliore e precisa! Di te!
Aqua,

20

Quanto segue riguarda le semplici funzioni C: in una classe C ++ il modificatore "statico" ha un altro significato.

Se hai un solo file, questo modificatore non fa assolutamente alcuna differenza. La differenza sta nei progetti più grandi con più file:

In C, ogni "modulo" (una combinazione di sample.c e sample.h) viene compilato in modo indipendente e successivamente ciascuno di questi file di oggetti compilati (sample.o) viene collegato insieme a un file eseguibile dal linker.

Supponiamo che tu abbia diversi file che includi nel tuo file principale e due di essi hanno una funzione che viene utilizzata solo internamente per comodità chiamata add(int a, b)- il compilatore creerebbe facilmente file oggetto per quei due moduli, ma il linker genererà un errore, perché trova due funzioni con lo stesso nome e non sa quale dovrebbe usare (anche se non c'è niente da collegare, perché non sono usate altrove ma nel suo file).

Questo è il motivo per cui si rende questa funzione, che viene utilizzata solo interna, una funzione statica. In questo caso il compilatore non crea la tipica "bandiera che puoi collegare" per il linker, in modo che il linker non veda questa funzione e non genererà un errore.


16

Primo: in genere è una cattiva idea includere un .cppfile in un altro file - porta a problemi come questo :-) Il modo normale è creare unità di compilazione separate e aggiungere un file di intestazione per il file incluso.

In secondo luogo:

Il C ++ ha una terminologia confusa qui - non lo sapevo fino a quando non è stato sottolineato nei commenti.

a) static functions- ereditato da C e di cosa stai parlando qui. Al di fuori di qualsiasi classe. Una funzione statica significa che non è visibile al di fuori dell'unità di compilazione corrente, quindi nel tuo caso a.obj ha una copia e l'altro codice ha una copia indipendente. (Gonfio dell'eseguibile finale con più copie del codice).

b) static member function- quale Orientamento agli oggetti definisce un metodo statico . Vive all'interno di una classe. Lo chiami con la classe anziché tramite un'istanza di oggetto.

Queste due diverse definizioni di funzioni statiche sono completamente diverse. Fai attenzione: ecco i draghi.


Bene, lo faccio solo per liberare un po 'di spazio TEMPORANEAMENTE in main.cpp fino a quando non decido come organizzare il file in librerie insieme a .hpp corretto. C'è un'idea migliore su come farlo?
Slava V,

1
La terminologia corretta in C ++ è la funzione membro, non il metodo. Non ci sono "metodi" in C ++ legalese. Il metodo è un termine OO generale. Il C ++ li implementa tramite le funzioni membro.
Brian Neal,

14

le definizioni delle funzioni statiche contrassegneranno questo simbolo come interno. Quindi non sarà visibile per il collegamento dall'esterno, ma solo per le funzioni nella stessa unità di compilazione, di solito lo stesso file.


7

Una funzione statica è quella che può essere chiamata sulla classe stessa, al contrario di un'istanza della classe.

Ad esempio un non statico sarebbe:

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

Questo metodo funziona su un'istanza della classe, non sulla classe stessa. Tuttavia, puoi avere un metodo statico che può funzionare senza un'istanza. Questo è talvolta usato nel modello Factory:

Person* tom = Person::createNewPerson();

2
Mi sembra che tu stia parlando di "metodo" statico, non di "funzione" ??
Slava V,

Supponevo che ti riferissi a funzioni statiche all'interno di una classe.
Pappagalli,

Se avessi saputo che "metodi" sono chiamati "funzioni di metodo" in C ++, ne sarei più chiaro. Bene, ora lo faccio :) Grazie comunque
Slava V,

5
Non ci sono "metodi" in C ++, solo funzioni. Lo standard C ++ non menziona mai "metodi", ma solo "funzioni".
Brian Neal,

1
@Puddle So cosa stai dicendo ma nello standard C ++ non esiste una definizione di "metodo". C ++ ha solo funzioni, di vario tipo. "Metodo" è un termine OO generale ed è utilizzato in altre lingue e in modo informale in C ++. Un metodo è formalmente noto come "funzione membro" in C ++.
Brian Neal,

7

Minor nit: le funzioni statiche sono visibili a un'unità di traduzione, che per la maggior parte dei casi pratici è il file in cui è definita la funzione. L'errore che si sta ottenendo viene comunemente chiamato violazione della regola di una definizione.

Lo standard probabilmente dice qualcosa di simile:

"Ogni programma deve contenere esattamente una definizione di ogni funzione o oggetto non in linea utilizzata in quel programma; non è richiesta alcuna diagnostica."

Questo è il modo C di guardare le funzioni statiche. Questo è deprecato in C ++ tuttavia.

In C ++, inoltre, è possibile dichiarare statiche le funzioni membro. Si tratta per lo più di metafunzioni, ovvero non descrivono / modificano il comportamento / lo stato di un particolare oggetto, ma agiscono sull'intera classe stessa. Inoltre, ciò significa che non è necessario creare un oggetto per chiamare una funzione membro statica. Inoltre, ciò significa anche che è possibile accedere a variabili membro statiche solo all'interno di tale funzione.

Aggiungerei all'esempio di Parrot il modello Singleton che si basa su questo tipo di funzione di un membro statico per ottenere / utilizzare un singolo oggetto per tutta la durata di un programma.


7

La risposta alla funzione statica dipende dalla lingua:

1) In lingue senza OOPS come C, significa che la funzione è accessibile solo all'interno del file in cui è definita.

2) In linguaggi con OOPS come C ++, significa che la funzione può essere chiamata direttamente sulla classe senza crearne un'istanza.


Questo non è vero. La spiegazione del secondo paragrafo si riferisce alle " funzioni membro statiche " di una classe, non alle " funzioni statiche ". In C ++, anche una funzione qualificata staticha un ambito di file, come in C.
RobertS supporta Monica Cellio l'

0

Poiché la funzione statica è visibile solo in questo file. In realtà, il compilatore può fare qualche ottimizzazione per te se dichiari "statico" per qualche funzione.

Qui c'è un semplice esempio.

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 compilare con

gcc -o main main.c

Lo vedrai fallito. Perché non implementi nemmeno la funzione ghost ().

Ma cosa succede se usiamo il seguente comando.

gcc -DTEST -O2 -o main main.c

Ha successo e questo programma può essere eseguito normalmente.

Perché? Ci sono 3 punti chiave.

  1. -O2: livello di ottimizzazione del compilatore almeno 2.
  2. -DTEST: Definisci TEST, quindi test () non verrà chiamato.
  3. Definito "statico" per test ().

Solo se queste 3 condizioni sono tutte vere, puoi passare la compilazione. A causa di questa dichiarazione "statica", il compilatore può confermare che test () non verrà MAI chiamato in un altro file. Il compilatore può rimuovere test () durante la compilazione. Poiché non abbiamo bisogno di test (), non importa se ghost () è definito o implementato.


0

" Che cos'è una" static"funzione in C? "

Cominciamo dall'inizio.

È tutto basato su una cosa chiamata "collegamento":

" Un identificatore dichiarato in diversi ambiti o nello stesso ambito più di una volta può essere fatto riferimento allo stesso oggetto o funzione tramite un processo chiamato collegamento. 29) Esistono tre tipi di collegamento: esterno, interno e nessuno. "

Fonte: C18, 6.2.2 / 1


"Nell'insieme di unità di traduzione e librerie che costituiscono un intero programma, ogni dichiarazione di un particolare identificatore con collegamento esterno indica lo stesso oggetto o funzione. All'interno di un'unità di traduzione, ogni dichiarazione di un identificatore con collegamento interno indica lo stesso oggetto o funzione . Ogni dichiarazione di un identificatore senza collegamento indica un'entità unica. "

Fonte: C18, 6.2.2 / 2


Se una funzione viene definita senza un identificatore della classe di archiviazione, externper impostazione predefinita la funzione ha un collegamento:

"Se la dichiarazione di un identificatore per una funzione non ha un identificatore della classe di archiviazione, il suo collegamento viene determinato esattamente come se fosse dichiarato con l'identificatore della classe di archiviazione esterno ."

Fonte: C18, 6.2.2 / 5

Ciò significa che - se il programma è contenuto in più unità di traduzione / file sorgente ( .co .cpp) - la funzione è visibile in tutte le unità di traduzione / file sorgente presenti nel programma.

Questo può essere un problema in alcuni casi. Che cosa succede se si desidera utilizzare due diverse funzioni (definizioni), ma con lo stesso nome di funzione in due contesti diversi (in realtà il contesto del file).

In C e C ++, il staticqualificatore della classe di archiviazione applicato a una funzione nell'ambito del file (non una funzione membro statica di una classe in C ++ o una funzione all'interno di un altro blocco) ora viene in aiuto e indica che la rispettiva funzione è visibile solo all'interno di l'unità di traduzione / file sorgente in cui è stata definita e non negli altri file / TLU.

"Se la dichiarazione di un identificatore di ambito file per un oggetto o una funzione contiene un identificatore di classe di memoria statico , l'identificatore ha un collegamento interno. 30)"


30) Una dichiarazione di funzione può contenere la classe di memoria specificatamente solo se è nell'ambito del file; vedi 6.7.1.

Fonte: C18, 6.2.2 / 3


Pertanto, una staticfunzione ha senso solo se:

  1. Il tuo programma è composto da diverse unità di traduzione / file sorgente ( .co .cpp).

    e

  2. Si desidera limitare l'ambito di una funzione al file, in cui è definita la funzione specifica.

Se entrambi questi requisiti non corrispondono, non è necessario chiudere la testa per qualificare una funzione come static.


Note laterali:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.