Come posso usare "sizeof" in una macro del preprocessore?


95

C'è un modo per utilizzare una sizeofin una macro del preprocessore?

Ad esempio, negli anni ci sono state un sacco di situazioni in cui volevo fare qualcosa come:

#if sizeof(someThing) != PAGE_SIZE
#error Data structure doesn't match page size
#endif

La cosa esatta che sto controllando qui è completamente inventata: il punto è che spesso mi piace inserire questi tipi di controlli in fase di compilazione (dimensione o allineamento) per proteggermi da qualcuno che modifichi una struttura dati che potrebbe disallineare o ri- dimensioni delle cose che le rompono.

Inutile dire che non mi sembra di essere in grado di utilizzare a sizeofnel modo descritto sopra.


Questo è il motivo esatto per cui esistono sistemi di compilazione.
Šimon Tóth

3
Questo è il motivo esatto per cui le direttive #error dovrebbero essere sempre tra virgolette doppie (costante di caratteri non terminata a causa di "non").
Jens

1
Ciao @ Brad. Si prega di considerare di cambiare la risposta accettata alla risposta di non importa, perché nel frattempo, la risposta attualmente accettata è diventata un po 'obsoleta.
Bodo Thiesen

@BodoThiesen Fatto.
Brad

Risposte:


69

Ci sono diversi modi per farlo. I seguenti frammenti non produrranno alcun codice se è sizeof(someThing)uguale a PAGE_SIZE; altrimenti produrranno un errore in fase di compilazione.

1. Modo C11

A partire da C11 puoi usare static_assert(richiede #include <assert.h>).

Utilizzo:

static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size");

2. Macro personalizzata

Se vuoi solo ottenere un errore in fase di compilazione quando sizeof(something)non è quello che ti aspetti, puoi usare la seguente macro:

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

Utilizzo:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

Questo articolo spiega in dettaglio perché funziona.

3. Specifico per SM

Sul compilatore Microsoft C ++ è possibile utilizzare la macro C_ASSERT (richiede #include <windows.h>), che utilizza un trucco simile a quello descritto nella sezione 2.

Utilizzo:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);

4
... è folle. Perché questa non è la risposta accettata, @Brad (OP)?
Ingegnere

Bel riferimento a BUILD_BUG_ON.
Petr Vepřek

2
La macro non funziona in GNU gcc(testato alla versione 4.8.4) (Linux). Al momento l' ((void)sizeof(...errore con expected identifier or '(' before 'void'e expected ')' before 'sizeof'. Ma in linea di principio size_t x = (sizeof(...invece funziona come previsto. Devi "usare" il risultato, in qualche modo. Per consentire che questo venga chiamato più volte all'interno di una funzione o in ambito globale, qualcosa di simile extern char _BUILD_BUG_ON_ [ (sizeof(...) ];può essere utilizzato ripetutamente (senza effetti collaterali, in realtà non si fa riferimento da _BUILD_BUG_ON_nessuna parte).
JonBrave

Uso affermazioni statiche da molto più tempo di quanto il 2011 sia passato un anno.
Dan

1
@ Ingegnere guarda, la follia si è fermata;)
Bodo Thiesen

70

C'è comunque da usare un " sizeof" in una macro pre-processore?

No. Le direttive condizionali accettano un insieme ristretto di espressioni condizionali; sizeofè una delle cose non consentite.

Le direttive di pre-elaborazione vengono valutate prima che la fonte venga analizzata (almeno concettualmente), quindi non ci sono ancora tipi o variabili per ottenere la loro dimensione.

Tuttavia, ci sono tecniche per ottenere asserzioni in fase di compilazione in C (ad esempio, vedere questa pagina ).


Ottimo articolo - soluzione intelligente! Anche se devi amministrare, hanno davvero spinto la sintassi C al suo limite per far funzionare questo! : -O
Brad

1
Risulta - come dice anche l'articolo - sto compilando il codice del kernel Linux in questo momento - e c'è già una definizione nel kernel - BUILD_BUG_ON - dove il kernel lo usa per cose come: BUILD_BUG_ON (sizeof (char)! = 8)
Brad il

2
@Brad BUILD_BUG_ON e altri generano codice sicuramente errato che non verrà compilato (e fornirà un messaggio di errore non ovvio in corso). Non proprio l'istruzione #if, quindi non è possibile escludere ad esempio blocchi di codice basati su questo.
keltar

10

So che è una risposta tardiva, ma per aggiungere alla versione di Mike, ecco una versione che usiamo che non alloca memoria. Non ho trovato il controllo della taglia originale, l'ho trovato su Internet anni fa e purtroppo non posso fare riferimento all'autore. Gli altri due sono solo estensioni della stessa idea.

Poiché sono typedef, non viene assegnato nulla. Con __LINE__ nel nome, è sempre un nome diverso, quindi può essere copiato e incollato dove necessario. Funziona nei compilatori MS Visual Studio C e nei compilatori GCC Arm. Non funziona in CodeWarrior, CW si lamenta della ridefinizione, non facendo uso del costrutto del preprocessore __LINE__.

//Check overall structure size
typedef char p__LINE__[ (sizeof(PARS) == 4184) ? 1 : -1];

//check 8 byte alignment for flash write or similar
typedef char p__LINE__[ ((sizeof(PARS) % 8) == 0) ? 1 : 1];

//check offset in structure to ensure a piece didn't move
typedef char p__LINE__[ (offsetof(PARS, SUB_PARS) == 912) ? 1 : -1];

Funziona davvero molto bene per un progetto C standard ... Mi piace!
Ashley Duncan

1
Questa dovrebbe essere la risposta corretta a causa dell'allocazione zero. Ancora meglio in una definizione:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato

p__LINE__ non produce un nome univoco. Produce p__LINE__ come variabile. Avresti bisogno di una macro preproc e userai __CONCAT da sys / cdefs.h.
Coroos

9

So che questo thread è davvero vecchio ma ...

La mia soluzione:

extern char __CHECK__[1/!(<<EXPRESSION THAT SHOULD COME TO ZERO>>)];

Finché quell'espressione è uguale a zero, viene compilata correttamente. Qualsiasi altra cosa ed esplode proprio lì. Poiché la variabile è esterna, non occuperà spazio e finché nessuno vi fa riferimento (cosa che non farà) non causerà un errore di collegamento.

Non è flessibile come la macro assert, ma non sono riuscito a compilare la macro nella mia versione di GCC e questo ... e penso che verrà compilato praticamente ovunque.


6
Non inventare mai le tue macro che iniziano con due trattini bassi. Questo percorso sta nella follia (ovvero comportamento indefinito ).
Jens

Ci sono un sacco di esempi elencati in questa pagina pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast

non funziona se compilato con il compilatore arm gcc. restituisce l'errore previsto "errore:" VERIFICA " modificato in modo variabile nell'ambito del file"
thunderbird

@ Jens Hai ragione, ma questa non è letteralmente una macro, è una dichiarazione di variabile. Ovviamente potrebbe interferire con le macro.
Melebius

4

Le risposte esistenti mostrano solo come ottenere l'effetto di "asserzioni in fase di compilazione" in base alla dimensione di un tipo. Ciò potrebbe soddisfare le esigenze dell'OP in questo caso particolare, ma ci sono altri casi in cui è davvero necessario un condizionatore del preprocessore basato sulla dimensione di un tipo. Ecco come farlo:

Scriviti un piccolo programma in C come:

/* you could call this sizeof_int.c if you like... */
#include <stdio.h>
/* 'int' is just an example, it could be any other type */
int main(void) { printf("%zd", sizeof(int); }

Compilalo. Scrivi uno script nel tuo linguaggio di scripting preferito, che esegue il programma C sopra e acquisisce il suo output. Usa quell'output per generare un file di intestazione C. Ad esempio, se stavi usando Ruby, potrebbe apparire come:

sizeof_int = `./sizeof_int`
File.open('include/sizes.h','w') { |f| f.write(<<HEADER) }
/* COMPUTER-GENERATED, DO NOT EDIT BY HAND! */
#define SIZEOF_INT #{sizeof_int}
/* others can go here... */
HEADER

Quindi aggiungi una regola al tuo Makefile o ad un altro script di build, che farà eseguire lo script sopra per compilare sizes.h.

Includere sizes.hovunque sia necessario utilizzare condizionali del preprocessore in base alle dimensioni.

Fatto!

(Hai mai digitato ./configure && makeper creare un programma? Quello che configurefanno gli script è fondamentalmente proprio come sopra ...)


è una cosa simile quando si usano strumenti come "autoconf".
Alexander Stohr

4

E la prossima macro:

/* 
 * Simple compile time assertion.
 * Example: CT_ASSERT(sizeof foo <= 16, foo_can_not_exceed_16_bytes);
 */
#define CT_ASSERT(exp, message_identifier) \
    struct compile_time_assertion { \
        char message_identifier : 8 + !(exp); \
    }

Ad esempio nel commento MSVC dice qualcosa come:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits

1
Questa non è una risposta alla domanda in quanto non è possibile utilizzarla in una #ifdirettiva del preprocessore.
cmaster - ripristina monica il

1

Proprio come riferimento per questa discussione, segnalo che alcuni compilatori ottengono sizeof () ar tempo di pre-processore.

La risposta di JamesMcNellis è corretta, ma alcuni compilatori passano attraverso questa limitazione (questo probabilmente viola lo stretto ansi c).

In questo caso, mi riferisco al compilatore IAR C (probabilmente il principale per microcontrollore professionale / programmazione embedded).


Sei sicuro di questo? IAR afferma che i loro compilatori sono conformi agli standard ISO C90 e C99, che non consentono la valutazione sizeofal momento della pre-elaborazione. sizeofdovrebbe essere trattato come un semplice identificatore.
Keith Thompson,

6
Nel 1998, qualcuno nel newsgroup comp.std.c scrisse: "Era bello ai tempi in cui cose come #if (sizeof(int) == 8)funzionavano (su alcuni compilatori)". La risposta: "Dev'essere stata prima del mio tempo", era di Dennis Ritchie.
Keith Thompson,

Ci scusiamo per la risposta in ritardo ... Sì, sono sicuro, ho esempi funzionanti di codice compilato per microcontrollori a 8/16/32 bit, compilatori Renesas (sia R8 che RX).
graziano governatori

In realtà, dovrebbe esserci qualche opzione per richiedere ISO C "rigorosi"
graziano governatori

non è una violazione dello standard fintanto che lo standard non lo proibisce. allora la definirei una caratteristica rara e non standard, quindi la eviterai in casi normali per mantenere l'indipendenza del compilatore e la portabilità della piattaforma.
Alexander Stohr

1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) potrebbe funzionare


Questa è una soluzione interessante, tuttavia funziona solo con variabili definite, non con i tipi. Un'altra soluzione che funziona con il tipo ma non con le variabili sarebbe:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet

7
Non funziona perché non puoi ancora usare il suo risultato all'interno di una #ifcondizione. Non fornisce alcun vantaggio sizeof(x).
Interjay

1

In C11 _Static_assertviene aggiunta la parola chiave. Può essere utilizzato come:

_Static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size")

0

Nel mio codice c ++ portabile ( http://www.starmessagesoftware.com/cpcclibrary/ ) volevo mettere una guardia sicura sulle dimensioni di alcuni dei miei struct o classi.

Invece di trovare un modo per il preprocessore di generare un errore (che non può funzionare con sizeof () come indicato qui), ho trovato una soluzione qui che fa sì che il compilatore generi un errore. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

Ho dovuto adattare quel codice per far sì che generasse un errore nel mio compilatore (xcode):

static union
{
    char   int8_t_incorrect[sizeof(  int8_t) == 1 ? 1: -1];
    char  uint8_t_incorrect[sizeof( uint8_t) == 1 ? 1: -1];
    char  int16_t_incorrect[sizeof( int16_t) == 2 ? 1: -1];
    char uint16_t_incorrect[sizeof(uint16_t) == 2 ? 1: -1];
    char  int32_t_incorrect[sizeof( int32_t) == 4 ? 1: -1];
    char uint32_t_incorrect[sizeof(uint32_t) == 4 ? 1: -1];
};

2
Sei sicuro che quei "-1" non verranno mai interpretati come 0xFFFF… FF, facendo sì che il tuo programma richieda tutta la memoria indirizzabile?
Anton Samsonov

0

Dopo aver provato le macro menzionate, questo frammento sembra produrre il risultato desiderato ( t.h):

#include <sys/cdefs.h>
#define STATIC_ASSERT(condition) typedef char __CONCAT(_static_assert_, __LINE__)[ (condition) ? 1 : -1]
STATIC_ASSERT(sizeof(int) == 4);
STATIC_ASSERT(sizeof(int) == 42);

In esecuzione cc -E t.h:

# 1 "t.h"
...
# 2 "t.h" 2

typedef char _static_assert_3[ (sizeof(int) == 4) ? 1 : -1];
typedef char _static_assert_4[ (sizeof(int) == 42) ? 1 : -1];

In esecuzione cc -o t.o t.h:

% cc -o t.o t.h
t.h:4:1: error: '_static_assert_4' declared as an array with a negative size
STATIC_ASSERT(sizeof(int) == 42);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
t.h:2:84: note: expanded from macro 'STATIC_ASSERT'
  ...typedef char __CONCAT(_static_assert_, __LINE__)[ (condition) ? 1 : -1]
                                                       ^~~~~~~~~~~~~~~~~~~~
1 error generated.

42 non è la risposta a tutto, dopotutto ...


0

Per controllare in fase di compilazione la dimensione delle strutture di dati rispetto ai loro vincoli ho usato questo trucco.

#if defined(__GNUC__)
{ char c1[sizeof(x)-MAX_SIZEOF_X-1]; } // brakets limit c1's scope
#else
{ char c1[sizeof(x)-MAX_SIZEOF_X]; }   
#endif

Se la dimensione di x è maggiore o uguale al suo limite MAX_SIZEOF_X, il gcc si lamenterà con un errore di "dimensione dell'array troppo grande". VC ++ genererà l'errore C2148 ("la dimensione totale dell'array non deve superare 0x7fffffff byte") o C4266 "non può allocare un array di dimensione costante 0".

Le due definizioni sono necessarie perché gcc consentirà di definire un array di dimensione zero in questo modo (sizeof x - n).


-10

L' sizeofoperatore non è disponibile per il preprocessore, ma puoi trasferirlo sizeofal compilatore e verificarne la condizione in runtime:

#define elem_t double

#define compiler_size(x) sizeof(x)

elem_t n;
if (compiler_size(elem_t) == sizeof(int)) {
    printf("%d",(int)n);
} else {
    printf("%lf",(double)n);
}

13
Come migliora la risposta già accettata? A cosa serve la definizione compiler_size? Cosa cerca di mostrare il tuo esempio?
ugoren
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.