Caratteristiche nascoste di C


141

So che esiste uno standard dietro tutte le implementazioni del compilatore C, quindi non dovrebbero esserci funzionalità nascoste. Nonostante ciò, sono sicuro che tutti gli sviluppatori C hanno trucchi nascosti / segreti che usano sempre.


Sarebbe bello se tu / qualcuno dovessi modificare la "domanda" per indicare la scelta delle migliori funzionalità nascoste, come nelle versioni C # e Perl di questa domanda.
Donal Fellows,

Risposte:


62

Puntatori a funzione. È possibile utilizzare una tabella di puntatori a funzione per implementare, ad esempio, interpreti di codice a thread indiretto veloce (FORTH) o dispatcher di codice byte, o per simulare metodi virtuali simili a OO.

Quindi ci sono gemme nascoste nella libreria standard, come qsort (), bsearch (), strpbrk (), strcspn () [gli ultimi due sono utili per implementare una sostituzione strtok ()].

Un malfunzionamento di C è che l'overflow aritmetico firmato è un comportamento indefinito (UB). Quindi, ogni volta che vedi un'espressione come x + y, entrambi essendo ints firmati, potrebbe potenzialmente traboccare e causare UB.


29
Ma se avessero specificato un comportamento in caso di overflow, avrebbe rallentato molto le architetture in cui quello non era il comportamento normale. L'overhead di runtime molto basso è sempre stato un obiettivo di progettazione di C e ciò ha significato che molte cose come questa non sono definite.
Mark Baker,

9
Sono ben consapevole del motivo per cui l' overflow è UB. È ancora un malfunzionamento, perché lo standard dovrebbe almeno fornire routine di libreria in grado di verificare l'overflow aritmetico (di tutte le operazioni di base) senza causare UB.
zvrba,

2
@zvrba, "routine di libreria in grado di verificare l'overflow aritmetico (di tutte le operazioni di base)" se lo si fosse aggiunto, si sarebbe verificato un notevole impatto sulle prestazioni per qualsiasi operazione aritmetica intera. ===== Caso di studio Matlab ADDS specifica la funzionalità di controllo del comportamento di overflow di numeri interi su avvolgimento o saturazione. E genera anche un'eccezione ogni volta che si verifica un overflow ==> Prestazioni delle operazioni con numeri interi Matlab: MOLTO LENTO. La mia conclusione: penso che Matlab sia un caso di studio convincente che mostra perché non si desidera il controllo di overflow di numeri interi.
Trevor Boyd Smith,

15
Ho detto che lo standard avrebbe dovuto fornire il supporto della libreria per il controllo dell'overflow aritmetico. Ora, come può una routine di libreria subire un hit da prestazione se non la usi mai?
zvrba,

5
Un grande aspetto negativo è che GCC non ha un flag per catturare overflow di numeri interi con segno e generare un'eccezione di runtime. Mentre ci sono flag x86 per rilevare tali casi, GCC non li utilizza. Avere un flag di questo tipo consentirebbe alle applicazioni non critiche per le prestazioni (soprattutto legacy) il vantaggio della sicurezza con revisione e refactoring minimi o nulli.
Andrew Keeton,

116

Più di un trucco del compilatore GCC, ma puoi dare suggerimenti di indicazione di ramo al compilatore (comune nel kernel di Linux)

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

vedi: http://kerneltrap.org/node/4705

Quello che mi piace di questo è che aggiunge anche un po 'di espressività ad alcune funzioni.

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}

2
Questo trucco è fantastico ... :) Soprattutto con le macro che definisci. :)
Sundar - Ripristina Monica il

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

Si tratta di un elemento facoltativo nello standard, ma deve essere una funzione nascosta, poiché le persone li ridefiniscono costantemente. Una base di codice su cui ho lavorato (e lo faccio ancora, per ora) ha più ridefinizioni, tutte con identificatori diversi. Il più delle volte è con le macro del preprocessore:

#define INT16 short
#define INT32  long

E così via. Mi fa venire voglia di strapparmi i capelli. Basta usare i caratteri tipografici interi standard eccentrici!


3
Penso che siano C99 o giù di lì. Non ho trovato un modo portatile per assicurarmi che esistessero.
akauppi,

3
Sono una parte facoltativa di C99, ma non conosco fornitori di compilatori che non lo implementano.
Ben Collins,

10
stdint.h non è opzionale in C99, ma apparentemente seguire lo standard C99 è per alcuni venditori ( tosse Microsoft).
Ben Combee,

5
@Pete, se vuoi essere anale: (1) Questo thread non ha nulla a che fare con qualsiasi prodotto Microsoft. (2) Questo thread non ha mai avuto nulla a che fare con C ++. (3) Non esiste C ++ 97.
Ben Collins

5
Dai un'occhiata a azillionmonkeys.com/qed/pstdint.h - uno stdint portatile vicino
gnud

73

L'operatore virgola non è ampiamente utilizzato. Può certamente essere maltrattato, ma può anche essere molto utile. Questo uso è il più comune:

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

Ma puoi usare questo operatore ovunque. Osservare:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

Ogni istruzione viene valutata, ma il valore dell'espressione sarà quello dell'ultima istruzione valutata.


7
In 20 anni di CI non l'ho mai visto!
Martin Beckett,

11
In C ++ puoi persino sovraccaricarlo.
Wouter Lievens,

6
can! = dovrebbe, ovviamente. Il pericolo di sovraccarico è che l'integrato si applica già a tutto, incluso il vuoto, quindi non mancherà mai di compilare per mancanza di sovraccarico disponibile. Cioè, dà al programmatore molta corda.
Aaron,

L'int all'interno del ciclo non funzionerà con C: è un miglioramento C ++. "," È la stessa operazione di (i = 0, j = 10; i <j; j--, i ++)?
Aif,

63

inizializzazione della struttura a zero

struct mystruct a = {0};

questo azzererà tutti gli elementi della struttura.


2
Tuttavia, non azzera l'imbottitura.
Mikeage

2
@simonn, no non ha un comportamento indefinito se la struttura contiene tipi non integrali. il memset con 0 sulla memoria di un float / double sarà comunque zero quando interpreterai il float / double (float / double sono progettati apposta come quello).
Trevor Boyd Smith,

6
@Andrew: memset/ callocdo "all bytes zero" (cioè zero fisici), che in effetti non è definito per tutti i tipi. { 0 } è garantito per intilaizzare tutto con valori logici zero adeguati . I puntatori, ad esempio, sono garantiti per ottenere i loro valori null corretti, anche se lo è il valore null sulla piattaforma data 0xBAADFOOD.
AnT

1
@nvl: ottieni zero fisico quando imposti con forza tutta la memoria occupata dall'oggetto sullo stato all-bit-zero. Questo è ciò che memsetfa ( 0come secondo argomento). Si ottiene lo zero logico quando si inizializza / assegna 0(o { 0 }) all'oggetto nel codice sorgente. Questi due tipi di zeri non producono necessariamente lo stesso risultato. Come nell'esempio con puntatore. Quando lo fai memsetsu un puntatore, ottieni un 0x0000puntatore. Ma quando si assegna 0a un puntatore, si ottiene un valore di puntatore nullo , che a livello fisico potrebbe essere 0xBAADF00Do qualsiasi altra cosa.
AnT

3
@nvl: Beh, in pratica la differenza è spesso solo concettuale. Ma in teoria, praticamente qualsiasi tipo può averlo. Ad esempio double,. Di solito è implementato secondo lo standard IEEE-754, in cui lo zero logico e lo zero fisico sono gli stessi. Ma IEEE-754 non è richiesto dalla lingua. Quindi potrebbe accadere che quando lo fai double d = 0;(zero logico), alcuni bit nella memoria occupati dnon saranno zero.
AnT

52

Costanti multi-carattere:

int x = 'ABCD';

Imposta xsu 0x41424344(o 0x44434241, a seconda dell'architettura).

EDIT: questa tecnica non è portatile, soprattutto se si serializza int. Tuttavia, può essere estremamente utile creare enum documentali. per esempio

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

Questo rende molto più semplice se stai guardando un dump di memoria grezza e devi determinare il valore di un enum senza doverlo cercare.


Sono abbastanza sicuro che questo non sia un costrutto portatile. Il risultato della creazione di una costante multi-carattere è definito dall'implementazione.
Mark Bessey,

8
I commenti "non portatili" mancano del tutto. È come criticare un programma per l'utilizzo di INT_MAX solo perché INT_MAX non è "portatile" :) Questa funzionalità è portatile come deve essere. La costante multi-carattere è una funzione estremamente utile che fornisce un modo leggibile per generare ID interi univoci.
AnT

1
@Chris Lutz - Sono abbastanza sicuro che la virgola finale risale a K&R. È descritto nella seconda edizione (1988).
Ferruccio,

1
@Ferruccio: devi pensare alla virgola finale negli elenchi di initailizer aggregati. Per quanto riguarda la virgola finale nelle dichiarazioni enum - è un'aggiunta recente, C99.
AnT

3
Hai dimenticato "HANG" o "BSOD" :-)
JBR Wilkinson,

44

Non ho mai usato i bit field ma suonano alla grande per cose di livello ultra basso.

struct cat {
    unsigned int legs:3;  // 3 bits for legs (0-4 fit in 3 bits)
    unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
    // ...
};

cat make_cat()
{
    cat kitty;
    kitty.legs = 4;
    kitty.lives = 9;
    return kitty;
}

Ciò significa che sizeof(cat)può essere piccolo come sizeof(char).


Commenti incorporati di Aaron e leppie , grazie ragazzi.


La combinazione di strutture e sindacati è ancora più interessante: su sistemi embedded o codice driver di basso livello. Un esempio è quando ti piace analizzare i registri di una scheda SD, puoi leggerlo usando union (1) e leggerlo usando union (2) che è una struttura di bitfield.
ComSubVie,

5
I campi di bit non sono portatili: il compilatore può scegliere liberamente se, nel tuo esempio, alle gambe verranno assegnati i 3 bit più significativi o i 3 bit meno significativi.
zvrba,

3
I bitfield sono un esempio di dove lo standard offre alle implementazioni tanta libertà nel modo in cui sono implementate, che in pratica sono quasi inutili. Se ti interessa quanti bit prende un valore e come viene memorizzato, è meglio usare maschere di bit.
Mark Bessey,

26
I bitfield sono effettivamente portatili fintanto che li trattate come gli elementi della struttura che sono, e non "pezzi di numeri interi". Le dimensioni, non la posizione, sono importanti in un sistema incorporato con memoria limitata, poiché ogni bit è prezioso ... ma la maggior parte dei programmatori di oggi sono troppo giovani per ricordarselo. :-)
Adam Liss,

5
@Adam: la posizione potrebbe essere importante in un sistema incorporato (o altrove), se si dipende dalla posizione del campo di bit nel suo byte. L'uso delle maschere rimuove qualsiasi ambiguità. Allo stesso modo per i sindacati.
Steve Melnikoff,

37

C ha uno standard ma non tutti i compilatori C sono completamente conformi (non ho ancora visto alcun compilatore C99 pienamente conforme!).

Detto questo, i trucchi che preferisco sono quelli non ovvi e portatili su tutte le piattaforme poiché si basano sul semantico C. Di solito si tratta di macro o bit aritmetica.

Ad esempio: scambiando due numeri interi senza segno senza utilizzare una variabile temporanea:

...
a ^= b ; b ^= a; a ^=b;
...

o "estendere C" per rappresentare macchine a stati finiti come:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

che può essere ottenuto con le seguenti macro:

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

In generale, tuttavia, non mi piacciono i trucchi che sono intelligenti ma rendono il codice inutilmente complicato da leggere (come esempio di scambio) e adoro quelli che rendono il codice più chiaro e trasmettono direttamente l'intenzione (come nell'esempio di FSM) .


18
C supporta il concatenamento, quindi puoi fare a ^ = b ^ = a ^ = b;
GU.

4
A rigor di termini, l'esempio di stato è un segno di spunta del preprocessore, e non del linguaggio C: è possibile utilizzare il primo senza il secondo.
Greg Whitfield,

15
GU: in realtà ciò che suggerisci è un comportamento indefinito a causa delle regole dei punti sequenza. Può funzionare sulla maggior parte dei compilatori, ma non è corretto o portatile.
Evan Teran,

5
Lo scambio Xor potrebbe effettivamente essere meno efficiente nel caso di un registro libero. Qualsiasi ottimizzatore decente renderebbe la variabile temp un registro. A seconda dell'implementazione (e della necessità del supporto del parallelismo) lo swap potrebbe effettivamente utilizzare la memoria reale anziché un registro (che sarebbe lo stesso).
Paul de Vrieze,

27
per favore, non farlo mai: en.wikipedia.org/wiki/…
Christian Oudard,

37

Strutture intrecciate come il dispositivo di Duff :

strncpy(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}

29
@ComSubVie, chiunque usi Duff's Device è uno script kiddy che ha visto Duff's Device e ha pensato che il loro codice sarebbe stato 1337 se avessero usato Duff's Device. (1.) Il dispositivo Duff non offre alcun aumento delle prestazioni sui processori moderni perché i processori moderni hanno un loop overhead zero. In altre parole, è un pezzo di codice obsoleto. (2.) Anche se il tuo processore non offre un ciclo zero overhead, probabilmente avrà qualcosa come SSE / altivec / elaborazione vettoriale che farà vergognare il tuo dispositivo Duff quando usi memcpy (). (3.) Ho già detto che non è utile fare memcpy () duff?
Trevor Boyd Smith,

2
@ComSubVie, per favore, conosci il mio pugno di morte ( en.wikipedia.org/wiki/… )
Trevor Boyd Smith,

12
@Trevor: quindi solo i programmi per bambini script 8051 e microcontrollori PIC, giusto?
SF.

6
@Trevor Boyd Smith: Mentre il dispositivo Duff sembra obsoleto, è ancora una curiosità storica, che convalida la risposta di ComSubVie. Ad ogni modo, citando Wikipedia: "Quando numerose istanze del dispositivo Duff furono rimosse dal server XFree86 nella versione 4.0, ci fu un notevole miglioramento delle prestazioni". ...
Paercebal,

2
Su Symbian, una volta abbiamo valutato vari loop per una rapida codifica dei pixel; il dispositivo del duff, in assemblatore, era il più veloce. Quindi ha avuto ancora rilevanza sui core ARM tradizionali sui tuoi smartphone oggi.
Sarà il

33

Mi piacciono molto gli inizializzatori designati, aggiunti in C99 (e supportati in gcc da molto tempo):

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

L'inizializzazione dell'array non dipende più dalla posizione. Se si modificano i valori di FOO o BAR, l'inizializzazione dell'array corrisponderà automaticamente al loro nuovo valore.


La sintassi gcc è supportata da molto tempo non è la stessa della sintassi C99 standard.
Mark Baker,

28

C99 ha una fantastica inizializzazione della struttura per qualsiasi ordine.

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}


27

strutture e array anonimi è il mio preferito. (cfr. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

o

void myFunction(type* values) {
    while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

può anche essere usato per istanziare liste collegate ...


3
Questa caratteristica è generalmente chiamata "letterali composti". Strutture anonime (o senza nome) designano strutture nidificate che non hanno nomi di membri.
calandoa,

secondo il mio CCG, "ISO C90 proibisce i composti letterali".
jmtd,

"ISO C99 supporta letterali composti." "Come estensione, GCC supporta valori letterali composti in modalità C89 e in C ++" (dixit info gcc). Inoltre, "Come estensione GNU, GCC consente l'inizializzazione di oggetti con durata di memorizzazione statica da valori letterali composti (cosa impossibile in ISO C99, poiché l'inizializzatore non è una costante)."
PypeBros,

24

gcc ha una serie di estensioni al linguaggio C che mi piacciono, che puoi trovare qui . Alcuni dei miei preferiti sono attributi di funzione . Un esempio estremamente utile è l'attributo format. Questo può essere usato se si definisce una funzione personalizzata che accetta una stringa di formato printf. Se abiliti questo attributo di funzione, gcc eseguirà dei controlli sugli argomenti per assicurarti che la stringa di formato e gli argomenti coincidano e genererà avvisi o errori nel modo appropriato.

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));

24

la funzione (nascosta) che mi ha "scioccato" quando ho visto per la prima volta riguarda printf. questa funzione consente di utilizzare le variabili per la formattazione degli identificatori di formato stessi. cerca il codice, vedrai meglio:

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

il carattere * ottiene questo effetto.


24

Beh ... penso che uno dei punti di forza del linguaggio C sia la sua portabilità e standardità, quindi ogni volta che trovo qualche "trucco nascosto" nell'implementazione che sto attualmente usando, provo a non usarlo perché provo a mantenere il mio Codice C il più standard e portatile possibile.


Ma in realtà, quanto spesso devi compilare il tuo codice con un altro compilatore?
Joe D

3
@Joe D se è un progetto multipiattaforma come Windows / OSX / Linux, probabilmente un po ', e c'è anche un arco diverso come x86 vs x86_64 ed ecc ...
Pharaun

@JoeD A meno che tu non sia in un progetto molto ristretto che è felice di sposare un fornitore di compilatore, molto. Potresti voler evitare effettivamente di cambiare compilatore, ma vuoi mantenere aperta questa opzione. Con i sistemi embedded, tuttavia, non sempre puoi scegliere. AHS, ASS.
XTL

19

Affermazioni in fase di compilazione, come già discusso qui .

//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
    typedef struct { \
        char static_assertion[condition ? 1 : -1]; \
    } static_assertion_t

//--- ensure structure fits in 
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);

16

Concatenazione di stringhe costante

Sono stato piuttosto sorpreso di non averlo già visto nelle risposte, poiché tutti i compilatori che conosco lo supportano, ma molti programmatori sembrano ignorarlo. A volte è davvero utile e non solo quando si scrivono macro.

Caso d'uso che ho nel mio codice attuale: ho un #define PATH "/some/path/" in un file di configurazione (in realtà è impostato dal makefile). Ora voglio costruire il percorso completo inclusi i nomi dei file per aprire le risorse. Va solo a:

fd = open(PATH "/file", flags);

Invece dell'orribile, ma molto comune:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

Si noti che la soluzione orribile comune è:

  • tre volte più a lungo
  • molto meno facile da leggere
  • molto più lento
  • meno potente impostato su un limite di dimensione del buffer arbitrario (ma dovresti usare un codice ancora più lungo per evitarlo senza una costante contatenazione di stringhe).
  • usa più spazio nello stack

1
È anche utile dividere una costante di stringa su più righe di origine senza usare `\` sporco.
dolmen,

15

Bene, non l'ho mai usato, e non sono sicuro se lo consiglierei mai a nessuno, ma penso che questa domanda sarebbe incompleta senza menzionare il trucco di routine di Simon Tatham .


12

Quando si inizializzano matrici o enumerazioni, è possibile inserire una virgola dopo l'ultimo elemento nell'elenco di inizializzatori. per esempio:

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

Questo è stato fatto in modo che se stai generando codice automaticamente non devi preoccuparti di eliminare l'ultima virgola.


Questo è importante anche in un ambiente multi-sviluppatore in cui, ad esempio, Eric aggiunge in "baz" e poi George aggiunge in "boom". Se Eric decide di estrarre il suo codice per la prossima build del progetto, si compila comunque con la modifica di George. Molto importante per il controllo del codice sorgente multi-ramo e la pianificazione di sviluppo sovrapposti.
Harold Bamford,

Enums può essere C99. Gli inizializzatori di matrice e la virgola finale sono K&R.
Ferruccio,

Gli enumerri semplici erano nel c89, AFAIK. Almeno sono stati in giro per secoli.
XTL

12

L'assegnazione di Struct è interessante. Molte persone non sembrano rendersi conto che anche le strutture sono valori e possono essere assegnate in giro, non è necessario utilizzarlememcpy() , quando un semplice compito fa la differenza.

Ad esempio, considera una libreria grafica 2D immaginaria, potrebbe definire un tipo per rappresentare una coordinata dello schermo (intera):

typedef struct {
   int x;
   int y;
} Point;

Ora fai cose che potrebbero sembrare "sbagliate", come scrivere una funzione che crea un punto inizializzato dagli argomenti della funzione e lo restituisce, in questo modo:

Point point_new(int x, int y)
{
  Point p;
  p.x = x;
  p.y = y;
  return p;
}

Questo è sicuro, fintanto che (ovviamente) il valore restituito viene copiato in base al valore usando l'assegnazione struct:

Point origin;
origin = point_new(0, 0);

In questo modo puoi scrivere codice ish abbastanza pulito e orientato agli oggetti, tutto in semplice standard C.


4
Naturalmente, ci sono implicazioni in termini di prestazioni nel passare attorno a grandi strutture in questo modo; è spesso utile (ed è davvero qualcosa che molte persone non capiscono che puoi fare) ma devi considerare se passare i puntatori è meglio.
Mark Baker,

1
Certo, ci potrebbe essere. È anche possibile che il compilatore rilevi l'utilizzo e lo ottimizzi.
Rilassati il

Fai attenzione se uno qualsiasi degli elementi è un puntatore, poiché copierai i puntatori stessi, non il loro contenuto. Naturalmente, lo stesso vale se si utilizza memcpy ().
Adam Liss,

Il compilatore non può ottimizzare questa conversione per valore passando con by-referenece, a meno che non possa eseguire ottimizzazioni globali.
Blaisorblade,

Probabilmente vale la pena notare che in C ++ lo standard consente specificamente di ottimizzare la copia (lo standard deve consentire ai compilatori di implementarlo perché significa che il costruttore di copie che può avere effetti collaterali non può essere chiamato), e poiché la maggior parte dei compilatori C ++ sono anche compilatori C, ci sono buone probabilità che il compilatore esegua questa ottimizzazione.
Joseph Garvin,

10

Indicizzazione vettoriale strana:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */

4
È ancora meglio ... char c = 2 ["Hello"]; (c == 'l' dopo questo).
anno

5
Non così strano se si considera che v [indice] == * (v + indice) e indice [v] == * (indice + v)
Ferruccio,

17
Per favore, dimmi che non lo usi davvero "tutto il tempo", come fa la domanda!
Prova il

9

I compilatori C implementano uno dei numerosi standard. Tuttavia, avere uno standard non significa che tutti gli aspetti della lingua siano definiti. Il dispositivo di Duff , ad esempio, è una delle funzionalità "nascoste" preferite che è diventata così popolare che i compilatori moderni hanno un codice di riconoscimento per scopi speciali per garantire che le tecniche di ottimizzazione non ostacolino l'effetto desiderato di questo modello spesso usato.

In generale, le funzioni nascoste o i trucchi del linguaggio sono sconsigliati mentre si esegue sul filo del rasoio di qualsiasi standard C utilizzato dal compilatore. Molti di questi trucchi non funzionano da un compilatore all'altro e spesso questo tipo di funzionalità fallisce da una versione di una suite di compilatori da un determinato produttore a un'altra versione.

Vari trucchi che hanno rotto il codice C includono:

  1. Affidarsi a come il compilatore espone le strutture in memoria.
  2. Ipotesi sull'endianità di numeri interi / float.
  3. Ipotesi su ABI di funzione.
  4. Ipotesi sulla direzione in cui crescono i frame dello stack.
  5. Ipotesi sull'ordine di esecuzione all'interno delle dichiarazioni.
  6. Ipotesi sull'ordine di esecuzione delle istruzioni negli argomenti delle funzioni.
  7. Presupposti sulla dimensione del bit o precisione di tipi corto, int, lungo, float e doppio.

Altri problemi e problemi che sorgono quando i programmatori fanno ipotesi sui modelli di esecuzione che sono tutti specificati nella maggior parte degli standard C come comportamento "dipendente dal compilatore".


Per risolvere la maggior parte di questi, fai in modo che tali ipotesi dipendano dalle caratteristiche della tua piattaforma e descrivi ciascuna piattaforma nella sua intestazione. L'esecuzione dell'ordine è un'eccezione: non fare mai affidamento su di essa; sulle altre idee, ogni piattaforma deve avere una decisione affidabile.
Blaisorblade,

2
@Blaisorblade, Ancora meglio, usa le asserzioni in fase di compilazione per documentare le tue assunzioni in modo da far fallire la compilazione su una piattaforma in cui vengono violate.
RBerteig,

Penso che si dovrebbero combinare entrambi, in modo che il codice funzioni su più piattaforme (questa era l'intenzione originale) e se le macro di funzionalità sono impostate nel modo sbagliato, le asserzioni in fase di compilazione lo cattureranno. Non sono sicuro se, per esempio, le assunzioni sulle ABI di funzioni siano verificabili come asserzioni in fase di compilazione, ma dovrebbe essere possibile per la maggior parte delle altre (valide) (tranne l'ordine di esecuzione ;-)).
Blaisorblade,

I controlli ABI delle funzioni devono essere gestiti da una suite di test.
dolmen,

9

Quando usi sscanf puoi usare% n per scoprire dove dovresti continuare a leggere:

sscanf ( string, "%d%n", &number, &length );
string += length;

Apparentemente, non puoi aggiungere un'altra risposta, quindi includerò una seconda qui, puoi usare "&&" e "||" come condizionali:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   1 || puts("Hello\n");
   0 || puts("Hi\n");
   1 && puts("ROFL\n");
   0 && puts("LOL\n");

   exit( 0 );
}

Questo codice genererà:

Ciao
ROFL

8

usare INT (3) per impostare il punto di interruzione nel codice è il mio preferito di tutti i tempi


3
Non penso che sia portatile. Funzionerà su x86, ma per quanto riguarda le altre piattaforme?
Cristian Ciupitu,

1
Non ne ho idea - Dovresti pubblicare una domanda al riguardo
Dror Helper l'

2
È una buona tecnica ed è specifica per X86 (anche se probabilmente ci sono tecniche simili su altre piattaforme). Tuttavia, questa non è una funzionalità di C. Dipende da estensioni C o chiamate alla libreria non standard.
Ferruccio,

1
In GCC c'è __builtin_trap e per MSVC __debugbreak che funzionerà su qualsiasi architettura supportata.
Axel Gneiting,

8

La mia funzione "nascosta" preferita di C, è l'uso di% n in printf per riscrivere nello stack. Normalmente printf estrae i valori dei parametri dallo stack in base alla stringa di formato, ma% n può riscriverli.

Dai un'occhiata alla sezione 3.4.2 qui . Può portare a molte cattive vulnerabilità.


il collegamento non funziona più, infatti il ​​sito stesso sembra non funzionare. Puoi fornire un altro link?
thequark

@thequark: qualsiasi articolo sulle "vulnerabilità delle stringhe di formato" contiene alcune informazioni al suo interno (es. crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf ) .. Tuttavia, a causa della natura del campo, la sicurezza i siti web stessi sono un po 'traballanti e articoli accademici reali sono difficili da trovare (con implementazione).
Sridhar Iyer,

8

Controllo dell'assunzione in fase di compilazione mediante enum: esempio stupido, ma può essere davvero utile per le librerie con costanti configurabili in fase di compilazione.

#define D 1
#define DD 2

enum CompileTimeCheck
{
    MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
    MAKE_SURE_DD_IS_POW2    = 1/((((DD) - 1) & (DD)) == 0)
};

2
+1 pulito. Usavo la macro CompilerAssert di Microsoft, ma la tua non è neanche male. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter,

1
Mi piace il metodo di enumerazione. L'approccio che avevo usato prima sfruttava l'eliminazione del codice morto: "if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}" che non ha commesso errori fino al tempo di collegamento, sebbene offrisse il vantaggio di lasciare il programmatore sa tramite messaggio di errore che il blorg è stato corteggiato.
supercat

8

Gcc (c) ha alcune caratteristiche divertenti che puoi abilitare, come dichiarazioni di funzioni nidificate e la forma a?: B dell'operatore?: Che restituisce a se a non è falso.


8

Ho scoperto di recente 0 bitfield.

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

che darà un layout di

000aaabb 0ccccddd

anziché senza: 0;

0000aaab bccccddd

Il campo di larghezza 0 indica che i seguenti campi di bit devono essere impostati sull'entità atomica successiva ( char)


7

Macro argomento variabile in stile C99, aka

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
                                  __VAR_ARGS__)

quale sarebbe usato come

ERR(errCantOpen, "File %s cannot be opened", filename);

Qui uso anche l'operatore stringize e la concatentazione costante di stringhe, altre funzionalità che mi piacciono molto.


Hai una 'R' extra in VA_ARGS .
Blaisorblade,

6

In alcuni casi sono utili anche variabili automatiche di dimensioni variabili. Questi sono stati aggiunti in nC99 e sono supportati da molto tempo in gcc.

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

Si finisce con un buffer nello stack con spazio per l'intestazione del protocollo di dimensioni fisse più i dati di dimensioni variabili. Puoi ottenere lo stesso effetto con alloca (), ma questa sintassi è più compatta.

Devi assicurarti che extraPadding sia un valore ragionevole prima di chiamare questa routine, o finisci per far esplodere lo stack. Dovresti controllare la correttezza degli argomenti prima di chiamare malloc o qualsiasi altra tecnica di allocazione della memoria, quindi non è davvero insolito.


Funzionerà anche correttamente se un byte / char non è esattamente largo 8 bit sulla piattaforma di destinazione? Lo so, quei casi sono rari, ma comunque ... :)
Stephan202,
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.