Promozioni degli argomenti predefiniti nelle chiamate alla funzione C.

Impostare

Ho alcune domande sulle promozioni degli argomenti predefiniti quando si chiama una funzione in C. Ecco la sezione 6.5.2.2 “Chiamate di funzione” paragrafi 6, 7 e 8 dello standard C99 (pdf) (enfasi aggiunta e suddivisa in elenchi per facilità di lettura):

Paragrafo 6

  1. Se l’espressione che denota la funzione chiamata ha un tipo che non include un prototipo , le promozioni intere vengono eseguite su ogni argomento e gli argomenti che hanno il tipo float vengono promossi a double . Queste sono chiamate promozioni di argomenti predefinite .
  2. Se il numero di argomenti non è uguale al numero di parametri, il comportamento non è definito.
  3. Se la funzione è definita con un tipo che include un prototipo e il prototipo termina con i puntini di sospensione ( , ... ) oi tipi di argomenti dopo la promozione non sono compatibili con i tipi dei parametri, il comportamento non è definito.
  4. Se la funzione è definita con un tipo che non include un prototipo , e i tipi di argomenti dopo la promozione non sono compatibili con quelli dei parametri dopo la promozione, il comportamento non è definito, ad eccezione dei seguenti casi:
    • un tipo promosso è un tipo intero con segno, l’altro tipo promosso è il corrispondente numero intero senza segno e il valore è rappresentabile in entrambi i tipi;
    • entrambi i tipi sono puntatori a versioni qualificate o non qualificate di un tipo di personaggio o void .

Paragrafo 7

  1. Se l’espressione che denota la funzione chiamata ha un tipo che include un prototipo , gli argomenti vengono convertiti implicitamente, come per attribuzione, ai tipi dei parametri corrispondenti, assumendo che il tipo di ciascun parametro sia la versione non qualificata del suo dichiarato genere.
  2. La notazione di ellissi in un dichiaratore di prototipo di funzione causa l’interruzione della conversione del tipo di argomento dopo l’ultimo parametro dichiarato. Le promozioni degli argomenti predefiniti vengono eseguite sugli argomenti finali.

Paragrafo 8

  1. Nessun’altra conversione viene eseguita implicitamente; in particolare, il numero e i tipi di argomenti non sono confrontati con quelli dei parametri in una definizione di funzione che non include un dichiaratore di prototipo di funzione .

Quello che so

  • Le promozioni di argomento predefinite sono char e short per int / unsigned int e float to double
  • Gli argomenti opzionali alle funzioni variadic (come printf ) sono soggetti alle promozioni degli argomenti predefiniti

Per la cronaca, la mia comprensione di un prototipo di funzione è questa:

 void func(int a, char b, float c); // Function prototype void func(int a, char b, float c) { /* ... */ } // Function definition 

Domanda

Sto attraversando un periodo davvero difficile, tutto questo. Ecco alcune domande che ho:

  • Il comportamento delle funzioni prototipate e non prototipate è davvero molto diverso, ad esempio per quanto riguarda le promozioni predefinite e le conversioni implicite?
  • Quando si verificano le promozioni degli argomenti predefiniti? È sempre? O è solo in casi speciali (come con le funzioni variadiche)? Dipende se una funzione è prototipata?

Votata la risposta di AProgrammer: quelli sono i veri beni.

Per quelli di voi che si stanno chiedendo perché le cose siano così: nell’era oscura prima del 1988, non esisteva un prototipo di funzione nel classico “K & R” C, e le promozioni di argomento predefinite venivano istituite perché (a) c’erano essenzialmente “libero”, in quanto non costa più mettere un byte in un registro piuttosto che mettere una parola in un registro e (b) ridurre i potenziali errori nel passaggio dei parametri. Questa seconda ragione non l’ha mai interrotta, ed è per questo che l’introduzione dei prototipi di funzione in ANSI C è stato il singolo cambiamento più importante in assoluto nel linguaggio C.

Per quanto riguarda le promozioni predefinite kick in: le promozioni degli argomenti predefinite vengono utilizzate esattamente quando il tipo atteso dell’argomento è sconosciuto , vale a dire quando non esiste un prototipo o quando l’argomento è variadic.

  • I parametri (non variadici) per le funzioni con un prototipo vengono convertiti nel tipo corrispondente, che può essere char, short, float.

  • I parametri per le funzioni senza parametri prototipo e variadic sono soggetti alle promozioni degli argomenti predefinite.

Se definisci una funzione con un prototipo e la usi senza il prototipo o viceversa e ha parametri di tipo char, short o float, probabilmente avrai un problema in fase di esecuzione. Avrai lo stesso tipo di problemi con le funzioni variadiche se il tipo promosso non corrisponde a quello usato durante la lettura dell’elenco degli argomenti.

Esempio 1: problema nel definire una funzione con un prototipo e utilizzarla senza.

definition.c

 void f(char c) { printf("%c", c); } 

use.c

 void f(); int main() { f('x'); } 

può fallire perché verrà passato un int e la funzione si aspetta un char.

Esempio 2: problema nel definire una funzione senza un prototipo e usarla con una.

definition.c

 void f(c) char c; { printf("%c", c); } 

(Questo tipo di definizione è molto vecchio stile)

use.c

 void f(char c); int main() { f('x'); } 

può fallire perché è previsto un int ma verrà passato un char.

Nota: noterai che tutte le funzioni della libreria standard hanno tipi risultanti da promozioni predefinite. Quindi non hanno causato problemi durante la transizione quando sono stati aggiunti i prototipi.

La tua confusione deriva da un leggerissimo fraintendimento della terminologia: sia le dichiarazioni che le definizioni possono includere prototipi (o meno):

 void func(int a, char b, float c); 

Questa è una dichiarazione di funzione che include un prototipo.

 void func(int a, char b, float c) { /* ... */ } 

Questa è una definizione di funzione che include un prototipo.

“Prototipato” e “non prototipato” sono solo attributi di un tipo di funzione, e sia le dichiarazioni che le definizioni introducono il tipo di funzione.

Quindi puoi avere una dichiarazione senza un prototipo:

 void func(); 

oppure puoi avere una definizione senza un prototipo (stile K & R C):

 void func(a, b, c) int a; char b; float c; { /* ... */ }