Comment puis-je utiliser «sizeof» dans une macro de préprocesseur?

95

Existe-t-il un moyen d'utiliser une sizeofmacro dans une macro de préprocesseur?

Par exemple, il y a eu une tonne de situations au fil des ans dans lesquelles je voulais faire quelque chose comme:

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

La chose exacte que je vérifie ici est complètement inventée - le fait est que j'aime souvent mettre ces types de vérifications au moment de la compilation (taille ou alignement) pour me prémunir contre quelqu'un qui modifie une structure de données qui pourrait désaligner ou ré-aligner dimensionner les choses qui les briseraient.

Inutile de dire que je ne semble pas être en mesure d'utiliser un sizeofde la manière décrite ci-dessus.

Brad
la source
C'est la raison exacte pour laquelle les systèmes de construction existent.
Šimon Tóth
3
C'est la raison exacte pour laquelle les directives #error doivent toujours être entre guillemets (constante de caractère non terminée en raison de "pas").
Jens
1
Bonjour @Brad. Veuillez envisager de changer votre réponse acceptée par la réponse de Nevermind, car entre-temps, la réponse actuellement acceptée est devenue un peu obsolète.
Bodo Thiesen
@BodoThiesen Terminé.
Brad

Réponses:

69

Il existe plusieurs façons de procéder. Les extraits de code suivants ne produiront aucun code s'ils sont sizeof(someThing)égaux PAGE_SIZE; sinon, ils produiront une erreur de compilation.

1. Voie C11

À partir de C11, vous pouvez utiliser static_assert(nécessite #include <assert.h>).

Usage:

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

2. Macro personnalisée

Si vous souhaitez simplement obtenir une erreur de compilation alors que ce sizeof(something)n'est pas ce à quoi vous vous attendez, vous pouvez utiliser la macro suivante:

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

Usage:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

Cet article explique en détail pourquoi cela fonctionne.

3. Spécifique à MS

Sur le compilateur Microsoft C ++, vous pouvez utiliser la macro C_ASSERT (requiert #include <windows.h>), qui utilise une astuce similaire à celle décrite dans la section 2.

Usage:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);
ça ne fait rien
la source
4
...C'est dingue. Pourquoi n'est-ce pas la réponse acceptée, @Brad (OP)?
Ingénieur
Belle référence à BUILD_BUG_ON.
Petr Vepřek
2
La macro ne fonctionne pas sous GNU gcc(testé à la version 4.8.4) (Linux). Au niveau des ((void)sizeof(...erreurs avec expected identifier or '(' before 'void'et expected ')' before 'sizeof'. Mais en principe, fonctionne size_t x = (sizeof(...plutôt comme prévu. Vous devez "utiliser" le résultat, d'une manière ou d'une autre. Pour permettre à cela d'être appelé plusieurs fois à l'intérieur d'une fonction ou à une portée globale, quelque chose comme extern char _BUILD_BUG_ON_ [ (sizeof(...) ];peut être utilisé à plusieurs reprises (pas d'effets secondaires, ne référencez à _BUILD_BUG_ON_aucun endroit).
JonBrave
J'utilise des assertions statiques depuis bien plus longtemps que 2011 a été un an.
Dan le
1
@Engineer look, la folie s'est arrêtée;)
Bodo Thiesen
70

Est-il possible d'utiliser un " sizeof" dans une macro de pré-processeur?

Non. Les directives conditionnelles acceptent un ensemble restreint d'expressions conditionnelles; sizeofest l'une des choses interdites.

Les directives de prétraitement sont évaluées avant que la source ne soit analysée (du moins sur le plan conceptuel), il n'y a donc pas encore de types ou de variables pour obtenir leur taille.

Cependant, il existe des techniques pour obtenir des assertions au moment de la compilation en C (par exemple, voir cette page ).

James McNellis
la source
Excellent article - solution intelligente! Bien que vous deviez administrer - ils ont vraiment poussé la syntaxe C à sa limite pour que celle-ci fonctionne! : -O
Brad
1
Il s'avère - comme le dit même l'article - que je construis actuellement le code du noyau Linux - et il y a déjà une définition dans le noyau - BUILD_BUG_ON - où le noyau l'utilise pour des choses comme: BUILD_BUG_ON (sizeof (char)! = 8)
Brad
2
@Brad BUILD_BUG_ON et d'autres générant du code sûrement incorrect qui échouera à se compiler (et donnera un message d'erreur non évident en cours). Pas vraiment une instruction #if, donc vous ne pouvez pas par exemple exclure un bloc de code basé sur cela.
keltar
10

Je sais que c'est une réponse tardive, mais pour ajouter à la version de Mike, voici une version que nous utilisons qui n'alloue aucune mémoire. Je n'ai pas trouvé le contrôle de taille d'origine, je l'ai trouvé sur Internet il y a des années et je ne peux malheureusement pas faire référence à l'auteur. Les deux autres ne sont que des extensions de la même idée.

Parce qu'ils sont typedef, rien n'est alloué. Avec le __LINE__ dans le nom, c'est toujours un nom différent afin qu'il puisse être copié et collé si nécessaire. Cela fonctionne dans les compilateurs MS Visual Studio C et les compilateurs GCC Arm. Cela ne fonctionne pas dans CodeWarrior, CW se plaint de la redéfinition, n'utilisant pas la construction de préprocesseur __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];
Paul
la source
Cela fonctionne vraiment bien pour un projet C standard ... je l'aime bien!
Ashley Duncan
1
Celui-ci devrait être la bonne réponse en raison de l'attribution zéro. Encore mieux dans une définition:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato
p__LINE__ ne produit pas de nom unique. Il produit p__LINE__ comme variable. Vous auriez besoin d'une macro preproc et utilisez __CONCAT de sys / cdefs.h.
Coroos
9

Je sais que ce fil est vraiment vieux mais ...

Ma solution:

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

Tant que cette expression équivaut à zéro, elle se compile correctement. Tout le reste et ça explose juste là. Parce que la variable est externe, elle ne prendra pas d'espace, et tant que personne ne la référence (ce qui n'est pas le cas), cela ne provoquera pas d'erreur de lien.

Pas aussi flexible que la macro assert, mais je ne pouvais pas faire compiler cela dans ma version de GCC et cela le sera ... et je pense qu'il se compilera à peu près n'importe où.

Scott
la source
6
N'inventez jamais vos propres macros en commençant par deux traits de soulignement. Ce chemin est la folie (alias comportement indéfini ).
Jens
Il y a un tas d'exemples répertoriés sur cette page pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast
ne fonctionne pas lorsqu'il est compilé avec le compilateur arm gcc. donne l'erreur attendue "erreur: ' CHECK ' modifié de manière variable à la portée du fichier"
thunderbird
@Jens Vous avez raison mais ce n'est littéralement pas une macro, c'est une déclaration de variable. Bien sûr, cela peut interférer avec les macros.
Melebius
4

Les réponses existantes montrent simplement comment obtenir l'effet des «assertions à la compilation» en fonction de la taille d'un type. Cela peut répondre aux besoins de l'OP dans ce cas particulier, mais il existe d'autres cas où vous avez vraiment besoin d'un préprocesseur conditionnel basé sur la taille d'un type. Voici comment procéder:

Écrivez-vous un petit programme C comme:

/* 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); }

Compilez ça. Écrivez un script dans votre langage de script préféré, qui exécute le programme C ci-dessus et capture sa sortie. Utilisez cette sortie pour générer un fichier d'en-tête C. Par exemple, si vous utilisiez Ruby, cela pourrait ressembler à:

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

Ensuite, ajoutez une règle à votre Makefile ou à un autre script de construction, ce qui le fera exécuter le script ci-dessus pour construire sizes.h.

Incluez sizes.hlà où vous avez besoin d'utiliser des conditions de préprocesseur basées sur des tailles.

Terminé!

(Avez-vous déjà tapé ./configure && makepour créer un programme? Ce que configurefont les scripts est essentiellement comme ci-dessus ...)

Alex D
la source
c'est une chose similaire lorsque vous utilisez des outils comme "autoconf".
Alexander Stohr
4

Qu'en est-il de la prochaine 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); \
    }

Par exemple, dans le commentaire, MSVC dit quelque chose comme:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits
Sergio
la source
1
Ce n'est pas une réponse à la question car vous ne pouvez pas l'utiliser dans une #ifdirective de préprocesseur.
cmaster - réintégrer monica
1

Juste à titre de référence pour cette discussion, je signale que certains compilateurs obtiennent sizeof () ar du temps pré-processeur.

La réponse de JamesMcNellis est correcte, mais certains compilateurs passent par cette limitation (cela viole probablement le strict ansi c).

Dans ce cas, je me réfère au compilateur IAR C (probablement le principal pour le microcontrôleur professionnel / la programmation embarquée).

gouverneur de Graziano
la source
Êtes-vous sûr de cela? L'IAR affirme que leurs compilateurs sont conformes aux normes ISO C90 et C99, qui ne permettent pas l'évaluation sizeofau moment du prétraitement. sizeofdoit être traité comme un simple identifiant.
Keith Thompson
6
En 1998, quelqu'un du groupe de discussion comp.std.c a écrit: "C'était bien à l'époque où des choses comme #if (sizeof(int) == 8)fonctionnaient réellement (sur certains compilateurs)." La réponse: "Ça devait être avant mon temps.", Était de Dennis Ritchie.
Keith Thompson
Désolé pour la réponse tardive ... Oui, je suis sûr, j'ai des exemples fonctionnels de code compilé pour des microcontrôleurs 8/16/32 bits, des compilateurs Renesas (à la fois R8 et RX).
graziano Governori
En fait, il devrait y avoir une option pour exiger une ISO C "stricte"
graziano Governori
ce n'est pas une violation de la norme tant que la norme ne l'interdit pas. alors je l'appellerais une fonctionnalité rare et non standard - ainsi vous l'éviterez dans des cas réguliers dans le but de garder l'indépendance du compilateur et la portabilité de la plate-forme.
Alexander Stohr
1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) pourrait fonctionner


la source
C'est une solution intéressante mais elle ne fonctionne qu'avec des variables définies, pas avec des types. Une autre solution qui fonctionne avec le type mais pas avec les variables serait:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet
7
Cela ne fonctionne pas car vous ne pouvez toujours pas utiliser son résultat dans une #ifcondition. Cela ne donne aucun avantage sizeof(x).
entre
1

En C11, le _Static_assertmot clé est ajouté. Il peut être utilisé comme:

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

Dans mon code c ++ portable ( http://www.starmessagesoftware.com/cpcclibrary/ ) je voulais mettre une garde sur la taille de certaines de mes structures ou classes.

Au lieu de trouver un moyen pour le préprocesseur de lancer une erreur (qui ne peut pas fonctionner avec sizeof () comme indiqué ici), j'ai trouvé ici une solution qui fait que le compilateur génère une erreur. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

J'ai dû adapter ce code pour qu'il génère une erreur dans mon compilateur (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];
};
Mike
la source
2
Etes-vous sûr que ces «-1» ne seront jamais interprétés comme 0xFFFF… FF, ce qui obligera votre programme à demander toute la mémoire adressable?
Anton Samsonov
0

Après avoir essayé les macros mentionnées, ce fragment semble produire le résultat souhaité ( 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);

Courir 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];

Courir 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 n'est pas la réponse à tout après tout ...

Coroos
la source
0

Pour vérifier au moment de la compilation la taille des structures de données par rapport à leurs contraintes, j'ai utilisé cette astuce.

#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

Si la taille de x est supérieure ou égale à sa limite MAX_SIZEOF_X, alors le gcc se plaindra d'une erreur «taille du tableau trop grande». VC ++ émettra soit l'erreur C2148 («la taille totale du tableau ne doit pas dépasser 0x7fffffff octets») soit C4266 «ne peut pas allouer un tableau de taille constante 0».

Les deux définitions sont nécessaires car gcc permettra de définir un tableau de taille zéro de cette façon (sizeof x - n).

Miguel de Reyna
la source
-10

L' sizeofopérateur n'est pas disponible pour le préprocesseur, mais vous pouvez transférer sizeofvers le compilateur et vérifier la condition à l'exécution:

#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);
}
Homme libre
la source
13
Comment améliore-t-il la réponse déjà acceptée? À quoi sert la définition compiler_size? Qu'est-ce que votre exemple essaie de montrer?
ugoren