2013-10-10 8 views
6

Spesso, oltre a fornire una dichiarazione di funzione, le intestazioni standard C possono fornire una "macro di mascheramento" per rendere le cose più veloci. Per esempio, se includo ctype.h, il file di intestazione dichiareràEsistono restrizioni allo standard C che consentono di implementare le funzioni come macro?

int isdigit(int c); 

Ma può anche mascherare la dichiarazione con una macro. Credo che questo è un portatile isdigit macro secondo lo standard C:

#define isdigit(c) ((c) >= '0' && (c) <= '9') 

Naturalmente, questa macro è anche pericolosa, perché introduce un comportamento indefinito se fate questo mentre la macro viene definita:

int c = 'A'; 
printf("%d\n", isdigit(c++)); 

Per evitare UB in questo caso ipotetico, devo circondare il nome della funzione con Parens: (isdigit)(c++). Quindi, la mia domanda è: ci sono restrizioni sul tipo di macro di mascheratura che può essere definita da un'intestazione standard? Sono garantiti per non causare un comportamento indefinito se un'espressione di argomento ha effetti collaterali, o tecnicamente sono autorizzati a comportarsi in modo strano, come vediamo sopra? Dove sono i limiti?

+0

La macro proposta non è conforme. Con le eccezioni delle implementazioni macro (opzionali) delle funzioni 'getc()' e 'putc()', una macro definizione di una funzione standard potrebbe non valutare nessuno dei suoi argomenti più di una volta. Se invocato come 'isdigit (C++)' la macro si comporta male, ma l'istruzione funziona correttamente con macro conformi e con una funzione. –

+0

Mi spiace, cosa intendi per "macro standard"? In ogni caso, quando ho detto "portatile", intendevo "ben educato su tutti i compilatori C conformi agli standard", quando ho passato un argomento appropriato senza effetti collaterali. –

+0

Lo standard C afferma che qualsiasi funzione standard può anche essere implementata come macro. Ma richiede anche che qualsiasi implementazione macro di qualsiasi funzione C standard (eccetto 'getc()' e 'putc()') debba solo valutare i suoi argomenti una volta, perché la macro deve comportarsi esattamente come se fosse una funzione. Inoltre, ogni funzione deve essere dichiarata e implementata come una funzione in modo che il suo indirizzo possa essere preso e passato come un puntatore alla funzione. Questo è praticamente l'informazione che Paul Griffiths ti ha citato dallo standard. Si noti che 'setjmp()' è esplicitamente una macro. –

risposta

5

Per C11 7.1.4.1, "Uso delle funzioni di libreria", in particolare l'ultima parte citato qui di seguito:

Qualsiasi funzione dichiarata in un colpo di testa può essere ulteriormente implementata come una macro funzione di come definita nel intestazione, quindi se una funzione di libreria viene dichiarata esplicitamente quando è inclusa la relativa intestazione, è possibile utilizzare una delle tecniche di seguito per garantire che la dichiarazione non sia interessata da una tale macro .... Per lo stesso motivo sintattico, è permesso di prendere l'indirizzo di una funzione di libreria anche se è anche definito come un ma cro. L'uso di #undef per rimuovere qualsiasi definizione di macro assicurerà inoltre che venga fatta riferimento a una funzione effettiva. Qualsiasi invocazione di una funzione di libreria implementata come macro deve espandere al codice che valuta ciascuno dei suoi argomenti esattamente una volta, completamente protetto da parentesi laddove necessario, quindi è in genere sicuro utilizzare espressioni arbitrarie come argomenti.

Si noti che il "generale" alla fine è importante, lì, e ci sono eccezioni esplicite. C11 7.21.7.5.2 dice "la funzione getc è equivalente a fgetc, con la differenza che se è implementata come macro, può valutare stream più di una volta, quindi l'argomento non dovrebbe mai essere un'espressione con effetti collaterali", con linguaggio simile per putc in C11 7.21.7.7.2. In questo caso specifico, stream è un FILE *, quindi in circostanze normali sarebbe strano avere questo come espressione con effetti collaterali, ma potrebbe accadere. Lo stesso vale anche per le loro controparti a carattere ampio, putwc() e getwc(). Non sono a conoscenza di altre eccezioni come questa.

+0

Interessante - Mi chiedo se i requisiti getc() e putc() siano effettivamente cambiati in C11 (o C99 per quella materia) ... vedi la mia risposta. –

+0

Penso che il "generale" sia importante nell'ultima frase. Per C11 7.21.7.5.2, "la funzione' getc' è equivalente a 'fgetc', tranne che se è implementata come macro, può valutare' stream' più di una volta, quindi l'argomento non dovrebbe mai essere un'espressione con effetti collaterali ", quindi penso che tu sia ancora corretto. –

+2

Le eccezioni 'getc()' e 'putc()' erano presenti in C89 (e C99) e C11. L'obiettivo è di consentire un'implementazione macro che normalmente evita qualsiasi chiamata di funzione, ma tali macro trovano difficile evitare di valutare l'argomento del flusso di file più volte. GCC fornisce 'espressioni di espressione' come un modo per avere tutto; sono, tuttavia, un'estensione GCC, non una caratteristica del compilatore C standard. –

1

Da qui: http://www.qnx.com/developers/docs/6.5.0/index.jsp?topic=%2Fcom.qnx.doc.dinkum_en_ecpp%2Flib_over.html

Argomenti che hanno effetti collaterali valutare allo stesso modo se l'espressione esegue la macro espansione o chiama la funzione. I macro per le funzioni getc e putc sono eccezioni esplicite a questa regola.

Ho trovato alcuni altri riferimenti che dicono la stessa cosa (anche se nulla cita direttamente lo standard C).

così sembra i limiti sono che solo getc() e putc() può causare il caos nel modo di prevedere. Oltre a questi due, dovresti essere sicuro, supponendo che la tua piattaforma sia almeno una di sana o conforme.