Perché viene compilata una funzione senza parametri (rispetto alla definizione della funzione effettiva)?


388

Ho appena trovato il codice C di qualcuno che sono confuso sul perché sta compilando. Ci sono due punti che non capisco.

Innanzitutto, il prototipo della funzione non ha parametri rispetto alla definizione della funzione effettiva. In secondo luogo, il parametro nella definizione della funzione non ha un tipo.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

Perché funziona? L'ho provato in un paio di compilatori e funziona benissimo.


77
È K&R C. Abbiamo scritto codice come questo negli anni '80 prima che esistessero prototipi a piena funzionalità.
Hughdbrown,

6
gcc avverte -Wstrict-prototypessia per int func()e int main(): xc: 3: warning: la dichiarazione di funzione non è un prototipo. Si dovrebbe dichiarare main()come main(void)pure.
Jens,

3
@Jens Perché hai modificato la domanda? Sembra che tu abbia perso il punto ...
dlras2

1
Ho appena reso esplicito l'int implicito. Come manca il punto? Credo che il punto sia il motivo per cui int func();è compatibile int func(arglist) { ... }.
Jens,

3
@MatsPetersson Questo è sbagliato. C99 5.1.2.2.1 contraddice espressamente la tua affermazione e afferma che la versione senza argomento è int main(void).
Jens,

Risposte:


271

Tutte le altre risposte sono corrette, ma solo per il completamento

Una funzione è dichiarata nel modo seguente:

  return-type function-name(parameter-list,...) { body... }

return-type è il tipo di variabile che restituisce la funzione. Questo non può essere un tipo di array o un tipo di funzione. Se non indicato, si assume int .

nome-funzione è il nome della funzione.

elenco parametri è l'elenco di parametri che la funzione accetta separati da virgole. Se non viene specificato alcun parametro, la funzione non accetta alcun valore e deve essere definita con un set di parentesi vuoto o con la parola chiave void. Se nessun tipo di variabile si trova davanti a una variabile nell'elenco dei parametri, si assume int . Le matrici e le funzioni non vengono passate alle funzioni, ma vengono automaticamente convertite in puntatori. Se l'elenco viene terminato con un puntino di sospensione (, ...), non esiste un numero di parametri impostato. Nota: l'intestazione stdarg.h può essere utilizzata per accedere agli argomenti quando si usano i puntini di sospensione.

E ancora per completezza. Dalla specifica C11 6: 11: 6 (pagina: 179)

L' uso di dichiaratori di funzioni con parentesi vuote (non dichiaratori di tipi di parametri in formato prototipo) è una caratteristica obsoleta .


2
"Se nessun tipo di variabile si trova davanti a una variabile nell'elenco dei parametri, allora si assume int." Vedo questo nel tuo link fornito, ma non riesco a trovarlo in nessuno standard c89, c99 ... Puoi fornire un'altra fonte?
godaygo,

"Se non viene fornito alcun parametro, la funzione non ne assume alcuna e dovrebbe essere definita con una serie vuota di parentesi" suona contraddittoria alla risposta di Tony The Lion, ma ho appena appreso che la risposta di Tony The Lion è corretta nel modo più duro.
Jakun

160

In C func()significa che puoi passare qualsiasi numero di argomenti. Se non vuoi argomenti, devi dichiarare come func(void). Il tipo che stai passando alla tua funzione, se non specificato per impostazione predefinita int.


3
In realtà non esiste un solo tipo predefinito a int. In effetti, tutti gli arg sono impostati su default (anche il tipo restituito). È perfettamente ok chiamare func(42,0x42);(dove tutte le chiamate devono usare il modulo args due).
Jens,

1
In C è abbastanza comune per i tipi non specificati di default int come ad esempio quando si dichiara una variabile: unsigned x; Di che tipo è la variabile x? Si scopre che il suo int
bitek

4
Preferirei dire che l'int implicito era comune ai vecchi tempi. Certamente non è più e nel C99 fu rimosso da C.
Jens il

2
Potrebbe essere utile notare che questa risposta si applica ai prototipi di funzioni con elenchi di parametri vuoti, ma non alle definizioni delle funzioni con elenchi di parametri vuoti.
autistico,

@mnemonicflow che non è un "tipo non specificato predefinito a int"; unsignedè solo un altro nome per lo stesso tipo di unsigned int.
Hobbs,

58

int func();è una dichiarazione di funzione obsoleta dai giorni in cui non esisteva lo standard C, ovvero i giorni di K&R C (prima del 1989, anno in cui fu pubblicato il primo standard "ANSI C").

Ricorda che non c'erano prototipi in K&R C e la parola chiave voidnon era ancora stata inventata. Tutto quello che potevi fare era dire al compilatore del tipo di ritorno di una funzione. L'elenco di parametri vuoto in K&R C significa "un numero non specificato ma fisso" di argomenti. Risolto significa che è necessario chiamare la funzione con lo stesso numero di arg ogni volta (al contrario di una funzione variadica come printf, in cui il numero e il tipo possono variare per ogni chiamata).

Molti compilatori diagnosticano questo costrutto; in particolare gcc -Wstrict-prototypesti dirà che "la dichiarazione di funzione non è un prototipo", il che è perfetto, perché sembra un prototipo (specialmente se sei avvelenato dal C ++!), ma non lo è. È una dichiarazione di tipo restituito K&R C vecchio stile.

Regola empirica: non lasciare mai vuota una dichiarazione dell'elenco parametri vuota, usare int func(void)per essere specifici. Ciò trasforma la dichiarazione del tipo di ritorno K&R in un prototipo C89 appropriato. I compilatori sono felici, gli sviluppatori sono felici, i controllori statici sono felici. Coloro che fuorviano di ^ W ^ Wfond di C ++ possono rabbrividire, perché devono digitare caratteri extra quando provano ad esercitare le loro abilità in lingua straniera :-)


1
Si prega di non mettere in relazione questo con C ++. È logico che un elenco di argomenti vuoto non contenga parametri e che le persone nel mondo non siano interessate a gestire gli errori commessi dai ragazzi di K&R circa 40 anni fa. Ma gli esperti del comitato continuano a trascinare scelte contranaturali lungo - in C99, C11.
pfalcon,

1
@pfalcon Il buon senso è abbastanza soggettivo. Il senso comune di una persona è la semplice follia per gli altri. I programmatori professionisti C sanno che elenchi di parametri vuoti indicano un numero non specificato ma fisso di argomenti. Una modifica che potrebbe probabilmente interrompere molte implementazioni. Incolpare i comitati per evitare cambiamenti silenziosi sta abbaiando sull'albero sbagliato, IMHO.
Jens,

53
  • L'elenco dei parametri vuoto significa "qualsiasi argomento", quindi la definizione non è sbagliata.
  • Si presume che il tipo mancante sia int.

Considererei che qualsiasi build che lo supera sia carente nel livello di avviso / errore configurato, non ha senso essere questo consentendo il codice effettivo.


30

È la dichiarazione e la definizione della funzione di stile K&R . Dallo standard C99 (ISO / IEC 9899: TC3)

Sezione 6.7.5.3 Dichiaratori di funzione (compresi i prototipi)

Un elenco di identificatori dichiara solo gli identificatori dei parametri della funzione. Un elenco vuoto in un dichiaratore di funzione che fa parte di una definizione di tale funzione specifica che la funzione non ha parametri. L'elenco vuoto in un dichiaratore di funzione che non fa parte di una definizione di tale funzione specifica che non vengono fornite informazioni sul numero o sui tipi dei parametri. (Se entrambi i tipi di funzione sono "vecchio stile", i tipi di parametro non vengono confrontati.)

Sezione 6.11.6 Dichiaratori di funzioni

L'uso di dichiaratori di funzioni con parentesi vuote (non dichiaratori di tipi di parametri in formato prototipo) è una caratteristica obsoleta.

Sezione 6.11.7 Definizioni delle funzioni

L'uso di definizioni di funzioni con identificatori di parametri separati ed elenchi di dichiarazioni (non tipo di parametro in formato prototipo e dichiaratori di identificatori) è una caratteristica obsoleta.

Che il vecchio stile significa K & R stile

Esempio:

Dichiarazione: int old_style();

Definizione:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}

16

C presuppone intse non viene fornito alcun tipo sul tipo di ritorno della funzione e sull'elenco dei parametri . Solo per questa regola è possibile seguire cose strane.

La definizione di una funzione è simile a questa.

int func(int param) { /* body */}

Se è un prototipo, scrivi

int func(int param);

Nel prototipo è possibile specificare solo il tipo di parametri. Il nome dei parametri non è obbligatorio. Così

int func(int);

Inoltre, se non si specifica il tipo di parametro ma il nome intviene assunto come tipo.

int func(param);

Se vai oltre, segui anche le opere.

func();

Il compilatore assume int func()quando scrivi func(). Ma non mettere func()dentro un corpo di funzione. Sarà una chiamata di funzione


3
Un elenco di parametri vuoto non è correlato al tipo int implicito. int func()non è una forma implicita di int func(int).
John Bartholomew,

11

Come affermato da @Krishnabhadra, tutte le precedenti risposte di altri utenti hanno un'interpretazione corretta e voglio solo fare un'analisi più dettagliata di alcuni punti.

In Old-C come in ANSI-C il " parametro formale non tipizzato ", prendere la dimensione del registro di lavoro o la capacità di profondità delle istruzioni (registri ombra o ciclo cumulativo delle istruzioni), in una MPU a 8 bit, sarà un int16, in una 16 bit MPU e così sarà un int16 e così via, nel caso in cui le architetture a 64 bit possano scegliere di compilare opzioni come: -m32.

Anche se sembra un'implementazione più semplice ad alto livello, Per passare più parametri, il lavoro del programmatore nella fase del tipo di dati dimencion di controllo, diventa più impegnativo.

In altri casi, per alcune architetture di microprocessori, i compilatori ANSI personalizzati, hanno sfruttato alcune di queste vecchie funzionalità per ottimizzare l'uso del codice, costringendo la posizione di questi "parametri formali non tipizzati" a funzionare all'interno o all'esterno del registro di lavoro, oggi ottieni quasi lo stesso con l'uso di "volatile" e "register".

Ma va notato che i compilatori più moderni, non fanno alcuna distinzione tra i due tipi di dichiarazione dei parametri.

Esempi di una compilation con gcc sotto linux:

main.c

main2.c

main3.c  
In ogni caso la dichiarazione del prototipo a livello locale non è di alcuna utilità, poiché non vi è alcuna chiamata senza parametri che rimandano a questo prototipo. Se si utilizza il sistema con "parametro formale non tipizzato", per una chiamata esterna, procedere con la generazione di un tipo di dati prototipo dichiarativo.

Come questo:

int myfunc(int param);

5
Ho preso la briga di ottimizzare le immagini per cui la dimensione in byte delle immagini, era il minimo possibile. Penso che le immagini possano avere una rapida visione della mancanza di differenza nel codice generato. Ho testato il codice, generando una documentazione reale di ciò che ha affermato e non solo teorizzando il problema. ma non tutti vedono il mondo allo stesso modo. Mi dispiace terribilmente che il mio tentativo di fornire ulteriori informazioni alla comunità abbia disturbato così tanto.
RTOSkit,

1
Sono quasi certo che un tipo di restituzione non specificato sia sempre int, che in genere è la dimensione del registro di lavoro o 16 bit, a seconda di quale sia minore.
supercat

@supercat +1 Deduzione perfetta, hai ragione! Questo è esattamente ciò di cui ho discusso sopra, un compilatore progettato per impostazione predefinita per un'architettura specifica, riflette sempre la dimensione del registro di lavoro della CPU / MPU in questione, quindi in un ambiente incorporato, ci sono varie strategie di riscrittura su un sistema operativo in tempo reale, in un livello di compilatore (stdint.h) o in un livello di portabilità, che rende portatile lo stesso sistema operativo in molte altre architetture e ottenuto dall'allineamento dei tipi specifici di CPU / MPU (int, long, long long, ecc.) con i tipi di sistema generici u8, u16, u32.
RTOSkit,

@supercat Senza i tipi di controllo offerti da un sistema operativo o da un compilatore specifico, devi avere un po 'di attenzione nel tempo di sviluppo e, fai in modo che tutti gli "incarichi non tipizzati" siano allineati con il design della tua applicazione, prima di avere la sorpresa di trovare un " 16bit int "e non un" 32bit int ".
RTOSkit,

@RTOSkit: la tua risposta suggerisce che il tipo predefinito su un processore a 8 bit sarà un Int8. Ricordo un paio di compilatori C-ish per l'architettura del marchio PICmicro dove era il caso, ma non credo che nulla di simile a uno standard C abbia mai permesso un inttipo che non era in grado di contenere tutti i valori nell'intervallo: Da 32767 a +32767 (non è richiesta la nota -32768) e anche in grado di contenere tutti i charvalori (il che significa che se charè 16 bit, deve essere firmato o intdeve essere più grande).
supercat

5

Per quanto riguarda il tipo di parametro, ci sono già risposte corrette qui, ma se vuoi ascoltarlo dal compilatore puoi provare ad aggiungere alcuni flag (i flag sono quasi sempre una buona idea).

compilando il tuo programma usando gcc foo.c -Wextraottengo:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

stranamente -Wextranon lo prende per clang(non lo riconosce -Wmissing-parameter-typeper qualche motivo, forse per quelli storici di cui sopra) ma -pedanticfa:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

E per il problema del prototipo, come detto sopra, si int func()fa riferimento a parametri arbitrari a meno che tu non lo definisca esplicitamente come int func(void)che quindi ti darebbe gli errori come previsto:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

o clangcome:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.

3

Se la dichiarazione di funzione non ha parametri cioè vuoti, allora sta prendendo un numero non specificato di argomenti. Se vuoi far sì che non prenda argomenti, cambialo in:

int func(void);

1
"Se il prototipo di funzione non ha parametri" Questo non è un prototipo di funzione, ma solo una dichiarazione di funzione.
effeffe

@effeffe l'ha risolto. Non avevo capito che questa domanda avrebbe suscitato molta attenzione;)
PP

3
"Prototipo di funzione" non è solo un'espressione alternativa (forse vecchio stile) per "dichiarazione di funzione"?
Giorgio,

@Giorgio IMO, entrambi sono corretti. "prototipo di funzione" deve corrispondere a "definizione di funzione". Probabilmente penso che effeffe significasse una dichiarazione nella domanda e ciò che intendevo è nella mia risposta.
PP

@Giorgio no, la dichiarazione di funzione è fatta dal valore del tipo restituito, dall'identificatore e da un elenco di parametri opzionale ; i prototipi di funzione sono una dichiarazione di funzione con un elenco di parametri.
effeffe

0

Questo è il motivo per cui in genere consiglio alle persone di compilare il loro codice con:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Queste bandiere impongono un paio di cose:

  • Dichiarazioni variabili variabili: è impossibile dichiarare una funzione non statica senza prima ottenere un prototipo. Ciò rende più probabile che un prototipo in un file di intestazione corrisponda alla definizione effettiva. In alternativa, impone di aggiungere la parola chiave statica a funzioni che non devono essere visibili pubblicamente.
  • -Wstrict-variabile-dichiarations: il prototipo deve elencare correttamente gli argomenti.
  • -Wold-style-definition: la stessa definizione della funzione deve anche elencare correttamente gli argomenti.

Questi flag sono anche usati di default in molti progetti Open Source. Ad esempio, FreeBSD ha questi flag abilitati quando si costruisce con WARNS = 6 nel tuo Makefile.

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.