Qual è il tuo trucco di programmazione C preferito? [chiuso]


134

Ad esempio, di recente mi sono imbattuto in questo nel kernel di Linux:

/ * Forza un errore di compilazione se la condizione è vera * /
#define BUILD_BUG_ON (condition) ((void) sizeof (char [1 - 2 * !! (condition)]))

Quindi, nel tuo codice, se hai una struttura che deve essere, diciamo un multiplo di 8 byte di dimensione, forse a causa di alcuni vincoli hardware, puoi fare:

BUILD_BUG_ON ((sizeof (struct mystruct)% 8)! = 0);

e non verrà compilato a meno che la dimensione di struct mystruct non sia un multiplo di 8 e se si tratta di un multiplo di 8, non viene generato alcun codice di runtime.

Un altro trucco che conosco è dal libro "Graphics Gems" che consente a un singolo file di intestazione di dichiarare e inizializzare le variabili in un modulo mentre in altri moduli che usano quel modulo, semplicemente dichiararle come esterne.

#ifdef DEFINE_MYHEADER_GLOBALS
#define GLOBAL
#define INIT (x, y) (x) = (y)
#altro
#define GLOBAL extern
#define INIT (x, y)
#finisci se

GLOBAL int INIT (x, 0);
GLOBAL int somefunc (int a, int b);

Con ciò, il codice che definisce x e somefunc fa:

#define DEFINE_MYHEADER_GLOBALS
#include "the_above_header_file.h"

mentre il codice che utilizza semplicemente x e somefunc () fa:

#include "the_above_header_file.h"

In questo modo si ottiene un file di intestazione che dichiara sia le istanze dei globuli che i prototipi delle funzioni dove sono necessari e le corrispondenti dichiarazioni esterne.

Quindi, quali sono i tuoi trucchi di programmazione C preferiti in questo senso?


9
Sembra più un trucco del preprocessore C.
jmucchiello,

A proposito di BUILD_BUG_ONmacro, cosa c'è di sbagliato nell'uso di #errorinside e #if?
Ricardo,

Risposte:


80

C99 offre alcune cose davvero interessanti usando array anonimi:

Rimozione di variabili inutili

{
    int yes=1;
    setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
}

diventa

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

Passare una quantità variabile di argomenti

void func(type* values) {
    while(*values) {
        x = *values++;
        /* do whatever with x */
    }
}

func((type[]){val1,val2,val3,val4,0});

Elenchi statici collegati

int main() {
    struct llist { int a; struct llist* next;};
    #define cons(x,y) (struct llist[]){{x,y}}
    struct llist *list=cons(1, cons(2, cons(3, cons(4, NULL))));
    struct llist *p = list;
    while(p != 0) {
        printf("%d\n", p->a);
        p = p->next;
    }
}

Sono sicuro che molte altre tecniche interessanti non ho pensato.


2
Credo che il tuo primo esempio possa anche essere scritto come &(int){1}, se vuoi rendere leggermente più chiaro quale sia il tuo intento qui.
Lily Ballard,

67

Durante la lettura del codice sorgente di Quake 2 mi è venuta in mente una cosa del genere:

double normals[][] = {
  #include "normals.txt"
};

(più o meno, non ho il codice a portata di mano per controllarlo ora).

Da allora, un nuovo mondo di uso creativo del preprocessore si è aperto davanti ai miei occhi. Non includo più solo le intestazioni, ma ogni tanto interi pezzi di codice (migliora molto la riusabilità) :-p

Grazie John Carmack! xD


13
Non puoi dire carmack in un thread di ottimizzazione senza menzionare il sqrt inverso veloce che era nella fonte del terremoto. en.wikipedia.org/wiki/Fast_inverse_square_root
pg1989

Da dove ha preso 0x5f3759df in primo luogo?
RSH1

2
@RoryHarvey: Da quello che ho potuto trovare guardandolo in su, sembra che fosse puramente empirico. Alcuni studi (non ricordo dove li ho visti) hanno dimostrato che era quasi ottimale, ma non del tutto ottimale. Allo stesso modo, sembra che per 64 bit sia stato scoperto il valore, piuttosto che il calcolo.
Matthieu M.

50

Mi piace usare = {0};l'inizializzazione delle strutture senza dover chiamare memset.

struct something X = {0};

Questo inizializzerà tutti i membri della struttura (o matrice) su zero (ma non sui byte di riempimento: utilizzare memset se è necessario azzerare anche quelli).

Ma dovresti essere consapevole che ci sono alcuni problemi con questo per le grandi strutture allocate dinamicamente .


A proposito, non è necessario per le variabili globali.
Strager

5
Non necessario per le variabili statiche . Le variabili globali possono essere azzerate, ma non è un requisito.
Jamie,

4
A volte lo estendo a: const struct something zero_something = { 0 };e quindi posso ripristinare una variabile al volo con struct something X = zero_something;o parzialmente attraverso una routine che posso usare 'X = zero_something;'. L'unica obiezione possibile è che comporta la lettura di dati da qualche parte; al giorno d'oggi un 'memset ()' potrebbe essere più veloce - ma mi piace la chiarezza dell'assegnazione ed è anche possibile usare valori diversi da zero anche nell'inizializzatore (e memset () seguito da modifiche al singolo membro potrebbe essere più lento di una semplice copia).
Jonathan Leffler,

45

Se stiamo parlando di trucchi c, il mio preferito deve essere il dispositivo di Duff per lo svolgimento di loop! Sto solo aspettando l'occasione giusta per trovarmi per usarlo davvero con rabbia ...


4
L'ho usato una volta per produrre un guadagno misurabile delle prestazioni, ma oggigiorno non è utile su molto hardware. Profila sempre!
Dan Olson

6
Sì, il tipo di persone che non comprendono il contesto in cui è stato creato il dispositivo di Duff: "leggibilità del codice" è inutile se il codice non è abbastanza veloce per funzionare. Probabilmente nessuna delle persone che hanno effettuato il downgrade ha mai dovuto programmare in tempo reale.
Rob K

1
+1, ho effettivamente avuto bisogno di usare il dispositivo Duff alcune volte. La prima volta è stato un loop che praticamente ha semplicemente copiato le cose e fatto alcune piccole trasformazioni lungo la strada. Era molto, molto più veloce di un semplice memcpy () in quell'architettura.
Makis,

3
La rabbia sarà dei tuoi colleghi e successori che dovranno mantenere il tuo codice dopo di te.
Jonathan Leffler,

1
Come ho già detto, sto ancora aspettando l'occasione giusta, ma nessuno mi ha ancora infastidito abbastanza. Scrivo C da circa 25 anni, penso di aver incontrato il dispositivo Duff nei primi anni '90 e non ho ancora dovuto usarlo. Come altri hanno già commentato, questo tipo di trucco è sempre meno utile in quanto i compilatori migliorano con questo tipo di ottimizzazione.
Jackson,

42

utilizzando __FILE__e __LINE__per il debug

#define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__);

6
Su alcuni compilatori ottieni anche FUNCTION .
JBR Wilkinson,

11
__FUNCTION__è solo un alias per __func__, ed __func__è in c99. Abbastanza utile. __PRETTY_FUNCTION__in C (GCC) è solo un altro alias per __func__, ma in C ++ otterrai la firma della funzione completa.
sklnd,

FILE mostra il percorso completo del nome file, quindi uso basename ( FILE )
Jeegar Patel

31

Nel C99

typedef struct{
    int value;
    int otherValue;
} s;

s test = {.value = 15, .otherValue = 16};

/* or */
int a[100] = {1,2,[50]=3,4,5,[23]=6,7};

28

Una volta un mio compagno e io abbiamo ridefinito il ritorno per trovare un insetto bug corruzione dello stack.

Qualcosa di simile a:

#define return DoSomeStackCheckStuff, return

4
Speriamo che # sia stato definito nel corpo della funzione e # indefinito alla fine!
Strager

Non mi piace molto - la prima cosa che mi viene in mente è che DoSomeStackCheckStuff rovina la memoria a causa di alcuni bug e chi sta leggendo il codice non è a conoscenza della ridefinizione del ritorno e si chiede cosa stia succedendo / hell /.
Gilligan,

8
@strager Ma questo lo renderebbe sostanzialmente inutile. Il punto è aggiungere una traccia ad ogni chiamata di funzione. Altrimenti aggiungeresti semplicemente una chiamata alle DoSomeStackCheckStufffunzioni che vuoi tracciare.
Clueless,

1
@gilligan Non penso che questo sia il tipo di cose che lasci sempre abilitate; sembra abbastanza utile per il lavoro di debug one-shot.
sunetos,

funziona davvero? :) Avrei scritto #define return if((DoSomeStackCheckStuff) && 0) ; else return... altrettanto pazzo immagino!
Paolo Bonzini,

22

Mi piace il "trucco hack" per avere un oggetto di dimensioni dinamiche. Questo sito lo spiega anche abbastanza bene (sebbene facciano riferimento alla versione C99 in cui è possibile scrivere "str []" come ultimo membro di una struttura). potresti creare una stringa "oggetto" come questo:

struct X {
    int len;
    char str[1];
};

int n = strlen("hello world");
struct X *string = malloc(sizeof(struct X) + n);
strcpy(string->str, "hello world");
string->len = n;

qui, abbiamo allocato una struttura di tipo X sull'heap delle dimensioni di un int (per len), più la lunghezza di "ciao mondo", più 1 (poiché str 1 è incluso nella dimensione di (X).

È generalmente utile quando si desidera avere un "header" proprio prima di alcuni dati di lunghezza variabile nello stesso blocco.


Personalmente trovo più facile semplicemente malloc () e realloc () e usare strlen () ogni volta che devo trovare la lunghezza, ma se hai bisogno di un programma che non conosce mai la lunghezza della stringa e probabilmente dovrai trovarne molte volte, questa è probabilmente la strada migliore.
Chris Lutz,

4
"... la versione C99 in cui è possibile scrivere" str [] "" Ho visto matrici di dimensioni zero in un contesto simile, come str [0]; abbastanza spesso. Penso che sia C99. So che i compilatori più anziani si lamentano di array di dimensioni zero.
smcameron,

3
Mi piace anche questo, tuttavia, dovresti usare qualcosa come malloc (offsetof (X, str) + numbytes) altrimenti le cose andranno male a causa di problemi di riempimento e allineamento. Ad esempio, sizeof (struct X) potrebbe essere 8, non 5.
Fozi,

3
@Fozi: in realtà non penso che sarebbe un problema. Poiché questa versione ha str[1](non str[]) 1 byte di str è incluso in sizeof(struct X). Ciò include qualsiasi imbottitura tra lene str.
Evan Teran,

2
@Rusky: come avrebbe influito negativamente su qualcosa? Supponiamo che ci sia "imbottitura" dopo str. OK, quando assegnerò sizeof(struct X) + 10Quindi questo rende strefficace 10 - sizeof(int)(o più, dal momento che abbiamo detto che c'è imbottitura) grande. Questo si sovrappone str e l'eventuale imbottitura successiva. L'unico modo in cui avrebbe qualche differenza, è se ci fosse un membro dopo il strquale rompe comunque tutto, i membri flessibili devono essere gli ultimi. Eventuali imbottiture alla fine causeranno probabilmente troppo allocazione. Fornisci un esempio specifico di come potrebbe effettivamente andare storto.
Evan Teran,

17

Codice orientato agli oggetti con C, emulando le classi.

Basta creare una struttura e un insieme di funzioni che puntino su quella struttura come primo parametro.


2
C'è ancora qualcosa là fuori che traduce C ++ in C, come al solito cfront?
MarkJ

11
Questo non è certo l'orientamento agli oggetti. Per OO con ereditarietà, è necessario aggiungere una sorta di tabella delle funzioni virtuali alla struttura dell'oggetto, che può essere sovraccaricata da "sottoclassi". Ci sono un sacco di framework in stile "C con classi" a metà là fuori per questo scopo, ma consiglio di starne fuori.
exDM69,

Bisognava dirlo. +1 per quello.
Am S

3
@ exDM69, l'orientamento agli oggetti è tanto un modo di pensare a un problema quanto un paradigma di codifica; puoi farlo con successo senza eredità. L'ho fatto su alcuni progetti prima di passare al nocciolo del C ++.
Mark Ransom,

16

Invece di

printf("counter=%d\n",counter);

Uso

#define print_dec(var)  printf("%s=%d\n",#var,var);
print_dec(counter);

14

Usare uno stupido trucco con le macro per rendere più semplice la gestione delle definizioni dei record.

#define COLUMNS(S,E) [(E) - (S) + 1]

typedef struct
{
    char studentNumber COLUMNS( 1,  9);
    char firstName     COLUMNS(10, 30);
    char lastName      COLUMNS(31, 51);

} StudentRecord;

11

Per creare una variabile che è di sola lettura in tutti i moduli tranne quello in cui è dichiarata:

// Header1.h:

#ifndef SOURCE1_C
   extern const int MyVar;
#endif

// Source1.c:

#define SOURCE1_C
#include Header1.h // MyVar isn't seen in the header

int MyVar; // Declared in this file, and is writeable

// Source2.c

#include Header1.h // MyVar is seen as a constant, declared elsewhere

Sembra pericoloso. Queste sono dichiarazioni e definizioni che non corrispondono. Durante la compilazione Source2.c, il compilatore potrebbe presumere che MyVarnon cambi, anche attraverso una chiamata di funzione a Source1.c. (Si noti che questo, come una variabile const reale, differisce da un puntatore a const. In quest'ultimo caso, l'oggetto puntato potrebbe ancora essere modificato tramite un puntatore diverso.)
jilles

1
Questo non produce variabili che sono di sola lettura solo in alcune unità di compilazione. Questo produce un comportamento indefinito (vedi p. 6.2.7.2 della ISO 9899 e anche p. 6.7.3.5).
Ales Hakl,

8

Gli spostamenti di bit sono definiti solo fino a un valore di spostamento di 31 (su un numero intero di 32 bit).

Cosa fai se vuoi avere un turno calcolato che deve funzionare anche con valori di spostamento più alti? Ecco come funziona il videodecec Theora:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  return (a>>(v>>1))>>((v+1)>>1);
}

O molto più leggibile:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  unsigned int halfshift = v>>1;
  unsigned int otherhalf = (v+1)>>1;

  return (a >> halfshift) >> otherhalf; 
}

Eseguire l'attività nel modo indicato sopra è molto più veloce dell'uso di un ramo come questo:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}

... e gcc lo allinea davvero :) +1
Tim Post

2
Sulla mia macchina, gcc-4.3.2 elimina il ramo nel secondo usando un'istruzione cmov (mossa condizionale)
Adam Rosenfield

3
"molto più veloce dell'uso di un ramo": la differenza è che il ramo è corretto per tutti i valori di v, mentre il halfshifttrucco raddoppia l'intervallo consentito a 63 su un'architettura a 32 bit e 127 su uno a 64 bit.
Pascal Cuoq,

8

Dichiarazione di array di puntatori a funzioni per l'implementazione di macchine a stati finiti.

int (* fsm[])(void) = { ... }

Il vantaggio più piacevole è che è semplice forzare ogni stimolo / stato a controllare tutti i percorsi del codice.

In un sistema incorporato, spesso mapperò un ISR per puntare a tale tabella e lo rivelerò secondo necessità (al di fuori dell'ISR).


Una tecnica che mi piace in questo caso è che, se si dispone di una funzione che richiede l'inizializzazione, si inizializza il puntatore con una chiamata alla routine di inizializzazione. Quando viene eseguito, l'ultima cosa che fa è sostituire il puntatore con un puntatore alla funzione effettiva, quindi chiamare quella funzione. In questo modo, l'inizializzatore viene chiamato automaticamente la prima volta che viene chiamata la funzione e la funzione reale viene chiamata ogni volta successiva.
TMN,

7

Un altro "trucco" pre-processore consiste nell'utilizzare il carattere "#" per stampare espressioni di debug. Per esempio:

#define MY_ASSERT(cond) \
  do { \
    if( !(cond) ) { \
      printf("MY_ASSERT(%s) failed\n", #cond); \
      exit(-1); \
    } \
  } while( 0 )

modificare: il codice seguente funziona solo su C ++. Grazie a smcameron e Evan Teran.

Sì, il tempo di compilazione asserito è sempre eccezionale. Può anche essere scritto come:

#define COMPILE_ASSERT(cond)\
     typedef char __compile_time_assert[ (cond) ? 0 : -1]

La macro COMPILE_ASSERT non può essere utilizzata due volte, poiché inquina lo spazio dei nomi con un typedef e il secondo utilizzo ottiene: errore: ridefinizione di typedef '__compile_time_assert'
smcameron

Hai davvero provato questo? Puoi "typedef foo;" tutte le volte che vuoi. È così che fai le dichiarazioni. L'ho usato per 2,5 anni su diversi compilatori, sia gcc, VC e un compilatore per un ambiente incorporato, e non ho mai riscontrato difficoltà.
Gilad Naor,

Odio il preprocessore C ... :(
hasen

1
Sì, l'ho provato. Ho tagliato e incollato il messaggio di errore dal compilatore, che era gcc.
smcameron,

1
@Gilad: è legale in c ++ avere typedef ridondanti, ma non in c.
Evan Teran,

6

Non lo definirei un trucco preferito, dal momento che non l'ho mai usato, ma la menzione di Duff's Device mi ha ricordato questo articolo sull'implementazione di Coroutine in C. Mi fa sempre ridere, ma sono sicuro che potrebbe essere utile qualche volta.


In realtà ho usato questa tecnica in pratica per rendere il codice che guida una sequenza di I / O asincroni dipendenti vagamente leggibile dall'uomo. La differenza principale è che non memorizzo lo stato coroutine in una staticvariabile, ma invece allocare dinamicamente una struttura e passare un puntatore a quello nella funzione coroutine. Un sacco di macro rendono questo più appetibile. Non è carino ma migliore della versione asincrona / callback che salta dappertutto. Userei i fili verdi (tramite swapcontext()* nixes) se potessi però.
pmdj,

6
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

The while (0); non ha alcun effetto sul programma, ma il compilatore emetterà un avviso su "questo non fa nulla", il che è abbastanza per farmi andare a guardare la linea offensiva e quindi vedere il vero motivo per cui volevo attirare l'attenzione su di esso.


9
non potresti usare #warning invece?
Stefano Borini,

Apparentemente, potrei. Non è completamente standard, ma ha funzionato nei compilatori che utilizzo. È interessante notare che il compilatore incorporato ha tradotto un #define, mentre gcc no.
Gbarry,

6

Sono un fan degli hack xor:

Scambia 2 puntatori senza puntatore terza temperatura:

int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;

O mi piace molto la lista dei link xor con un solo puntatore. (Http://en.wikipedia.org/wiki/XOR_linked_list)

Ogni nodo nell'elenco collegato è lo Xor del nodo precedente e del nodo successivo. Per spostarsi in avanti, l'indirizzo dei nodi si trova nel modo seguente:

LLNode * first = head;
LLNode * second = first.linked_nodes;
LLNode * third = second.linked_nodes ^ first;
LLNode * fourth = third.linked_nodes ^ second;

eccetera.

o per attraversare all'indietro:

LLNode * last = tail;
LLNode * second_to_last = last.linked_nodes;
LLNode * third_to_last = second_to_last.linked_nodes ^ last;
LLNode * fourth_to_last = third_to_last.linked_nodes ^ second_to_last;

eccetera.

Sebbene non sia terribilmente utile (non puoi iniziare a spostarti da un nodo arbitrario) lo trovo molto interessante.


5

Questo è tratto dal libro "Abbastanza corda per spararti ai piedi":

Nell'intestazione dichiarare

#ifndef RELEASE
#  define D(x) do { x; } while (0)
#else
#  define D(x)
#endif

Nel codice inserire dichiarazioni di test, ad esempio:

D(printf("Test statement\n"));

Il do / while aiuta nel caso in cui il contenuto della macro si espanda a più istruzioni.

L'istruzione verrà stampata solo se non viene utilizzato il flag '-D RELEASE' per il compilatore.

È quindi possibile ad es. passa la bandiera al tuo makefile ecc.

Non sono sicuro di come funzioni in Windows, ma in * nix funziona bene


Potresti voler espandere D (x) su {} quando è definito RELEASE, in modo che funzioni bene con le istruzioni if. Altrimenti "if (a) D (x);" si espanderà fino a "if (a)" quando avrai definito RELEASE. Questo ti darà alcuni simpatici bug nella versione RELEASE
MarkJ

3
@MarkJ: NO. Così com'è, "if (a) D (x);" si espande in "if (a);" che va benissimo. Se hai avuto D (x) espandere su {}, quindi "if (a) if (b) D (x); else foo ();" si espanderebbe in modo NON CORRETTO a "if (a) if (b) {}; else foo ();", facendo corrispondere "else foo ()" al secondo if anziché al primo if.
Adam Rosenfield,

Ad essere sincero, uso principalmente questa macro per testare le dichiarazioni di stampa, o se avessi una dichiarazione condizionale la racchiuderei ad es. D (if (a) foo (););
Simon Walker

1
@AdamRosenfield: Usare #define D(x) do { } while(0)invece gestisce quel caso (e può essere applicato al ramo che si inserisce xanche per coerenza)
rpetrich

3

Rusty ha effettivamente prodotto un intero set di condizionali build in ccan , controlla il modulo assert build:

#include <stddef.h>
#include <ccan/build_assert/build_assert.h>

struct foo {
        char string[5];
        int x;
};

char *foo_string(struct foo *foo)
{
        // This trick requires that the string be first in the structure
        BUILD_ASSERT(offsetof(struct foo, string) == 0);
        return (char *)foo;
}

Ci sono molte altre macro utili nell'intestazione reale, che sono facili da inserire.

Cerco, con tutte le mie forze, di resistere all'attrazione del lato oscuro (e dell'abuso del preprocessore) attenendomi principalmente alle funzioni incorporate, ma mi piacciono le macro intelligenti e utili come quelle che hai descritto.


Sì, di recente mi sono imbattuto in ccan e stavo prendendo in considerazione l'idea di contribuire con un po 'di codice, ma non ho ancora avvolto la mia testa nella "via del ccan". Grazie per il link, più motivazione per guardare in Ccan, che spero davvero che abbia una certa trazione.
smcameron

Bene, non mi preoccuperei troppo della "via del ccan" fino a quando non sarà più consolidata ... in questo momento il ccan-lint viene proposto come progetto GSOC. È un gruppo piccolo e piuttosto amichevole .. e un ottimo posto per scaricare frammenti :)
Tim Post

A proposito, ho notato che BuILD_ASSERT di Rusty è proprio come la macro del kernel Linux (non sorprende) ma manca uno dei "nots" (o bangs o!) E notando che, penso che il mio esempio di utilizzo della macro che ho pubblicato sia Non corretto. Avrebbe dovuto essere: "BUILD_BUG_ON ((sizeof (struct mystruct)% 8))"
smcameron

3

Due buoni libri di riferimento per questo genere di cose sono The Practice of Programming and Writing Solid Code . Uno di questi (non ricordo quale) dice: Preferisci enum a #define dove puoi, perché enum viene controllato dal compilatore.


1
AFAIK, in C89 / 90 NON c'è alcun controllo di battitura per enumerazioni. gli enum sono solo in qualche modo #define più convenienti.
cschol

In fondo a pagina 39, 2 ° ED K&R. C'è almeno l'opportunità di verificare.
Jonathan Watmough,

3

Non specifico per C, ma mi è sempre piaciuto l'operatore XOR. Una cosa interessante che può fare è "scambiare senza un valore temporaneo":

int a = 1;
int b = 2;

printf("a = %d, b = %d\n", a, b);

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

printf("a = %d, b = %d\n", a, b);

Risultato:

a = 1, b = 2

a = 2, b = 1


a = 1; b = 2; a = a + b; b = ab; a = ab; dà lo stesso risultato anche
Grambot

Questo sostituirà anche a e b: a ^ = b ^ = a ^ = b;
vikhyat,

@TheCapn: l'addizione potrebbe traboccare, però.
Michael Foukarakis,


2

Mi piace il concetto di container_ofusato ad esempio negli elenchi. Fondamentalmente, non è necessario specificare nexte lastcampi per ogni struttura che sarà nell'elenco. Invece, si aggiunge l'intestazione della struttura dell'elenco agli elementi effettivamente collegati.

Dai un'occhiata include/linux/list.hagli esempi di vita reale.


1

Penso che l'uso dei puntatori userdata sia piuttosto accurato. Una moda che perde terreno oggi. Non è tanto una caratteristica C ma è abbastanza facile da usare in C.


1
Vorrei aver capito cosa intendevi qui. Potresti spiegare di più? Che cos'è un puntatore dati utente?
Zan Lynx,


è principalmente per i callback. Si tratta di alcuni dati che vorresti essere restituiti ogni volta che viene attivato il callback. Particolarmente utile per passare un puntatore C ++ a un callback in modo da poter associare un oggetto a un evento.
Evan Teran,

Ah sì. Grazie. Lo uso molto, ma non l'ho mai chiamato così.
Zan Lynx,

1

Uso X-Macros per consentire al pre-compilatore di generare codice. Sono particolarmente utili per definire i valori di errore e le stringhe di errore associate in un unico punto, ma possono andare ben oltre.


1

La nostra base di codice ha un trucco simile a

#ifdef DEBUG

#define my_malloc(amt) my_malloc_debug(amt, __FILE__, __LINE__)
void * my_malloc_debug(int amt, char* file, int line)
#else
void * my_malloc(int amt)
#endif
{
    //remember file and line no. for this malloc in debug mode
}

che consente il tracciamento delle perdite di memoria in modalità debug. Ho sempre pensato che fosse bello.


1

Divertimento con le macro:

#define SOME_ENUMS(F) \
    F(ZERO, zero) \
    F(ONE, one) \
    F(TWO, two)

/* Now define the constant values.  See how succinct this is. */

enum Constants {
#define DEFINE_ENUM(A, B) A,
    SOME_ENUMS(DEFINE_ENUMS)
#undef DEFINE_ENUM
};

/* Now a function to return the name of an enum: */

const char *ToString(int c) {
    switch (c) {
    default: return NULL; /* Or whatever. */
#define CASE_MACRO(A, B) case A: return #b;
     SOME_ENUMS(CASE_MACRO)
#undef CASE_MACRO
     }
}

0

Ecco un esempio di come rendere il codice C completamente ignaro di ciò che viene effettivamente utilizzato da HW per l'esecuzione dell'app. Il main.c esegue l'installazione e quindi il layer libero può essere implementato su qualsiasi compilatore / arco. Penso che sia abbastanza pulito per sottrarre un po 'il codice C, quindi non può essere troppo specifico.

Aggiungendo un esempio compilabile completo qui.

/* free.h */
#ifndef _FREE_H_
#define _FREE_H_
#include <stdio.h>
#include <string.h>
typedef unsigned char ubyte;

typedef void (*F_ParameterlessFunction)() ;
typedef void (*F_CommandFunction)(ubyte byte) ;

void F_SetupLowerLayer (
F_ParameterlessFunction initRequest,
F_CommandFunction sending_command,
F_CommandFunction *receiving_command);
#endif

/* free.c */
static F_ParameterlessFunction Init_Lower_Layer = NULL;
static F_CommandFunction Send_Command = NULL;
static ubyte init = 0;
void recieve_value(ubyte my_input)
{
    if(init == 0)
    {
        Init_Lower_Layer();
        init = 1;
    }
    printf("Receiving 0x%02x\n",my_input);
    Send_Command(++my_input);
}

void F_SetupLowerLayer (
    F_ParameterlessFunction initRequest,
    F_CommandFunction sending_command,
    F_CommandFunction *receiving_command)
{
    Init_Lower_Layer = initRequest;
    Send_Command = sending_command;
    *receiving_command = &recieve_value;
}

/* main.c */
int my_hw_do_init()
{
    printf("Doing HW init\n");
    return 0;
}
int my_hw_do_sending(ubyte send_this)
{
    printf("doing HW sending 0x%02x\n",send_this);
    return 0;
}
F_CommandFunction my_hw_send_to_read = NULL;

int main (void)
{
    ubyte rx = 0x40;
    F_SetupLowerLayer(my_hw_do_init,my_hw_do_sending,&my_hw_send_to_read);

    my_hw_send_to_read(rx);
    getchar();
    return 0;
}

4
Cura di elaborare, forse spiegando un uso pratico?
Leonardo Herrera,

Come esempio se devo scrivere un programma di test usando un'interfaccia HW che alla fine genera interruzioni. Quindi questo modulo può essere configurato per eseguire una funzione al di fuori del normale ambito come gestore di segnale / interruzione.
eaanon01,

0
if(---------)  
printf("hello");  
else   
printf("hi");

Riempi gli spazi vuoti in modo che né ciao né ciao compaiano in output.
ans:fclose(stdout)


puoi formattare il codice con il {}pulsante della barra degli strumenti (l'ho fatto per te). Il pulsante "Preventivo" non mantiene gli spazi bianchi né applica l'evidenziazione della sintassi.
Álvaro González
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.