Wie kann ich "sizeof" in einem Präprozessor-Makro verwenden?

95

Gibt es eine Möglichkeit, a sizeof in einem Präprozessor-Makro zu verwenden?

Zum Beispiel gab es im Laufe der Jahre eine Menge Situationen, in denen ich etwas tun wollte wie:

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

Das genaue, was ich hier überprüfe, ist vollständig erfunden - der Punkt ist, dass ich häufig diese Arten von Überprüfungen zur Kompilierungszeit (Größe oder Ausrichtung) einsetze, um zu verhindern, dass jemand eine Datenstruktur ändert, die falsch ausgerichtet oder neu ausgerichtet werden könnte Größe Dinge, die sie brechen würden.

Unnötig zu sagen - ich scheine nicht in der Lage zu sein, a sizeofin der oben beschriebenen Weise zu verwenden.

Brad
quelle
Dies ist der genaue Grund, warum Build-Systeme existieren.
Šimon Tóth
3
Dies ist der genaue Grund, warum # Fehler-Direktiven immer in doppelten Anführungszeichen stehen sollten (nicht abgeschlossene Zeichenkonstante aufgrund von "nicht").
Jens
1
Hallo @Brad. Bitte erwägen Sie, Ihre akzeptierte Antwort in die Antwort von nevermind zu ändern, da die aktuell akzeptierte Antwort in der Zwischenzeit etwas veraltet ist.
Bodo Thiesen
@ BodoThiesen Fertig.
Brad

Antworten:

69

Es gibt verschiedene Möglichkeiten, dies zu tun. Das Folgen von Snippets erzeugt keinen Code, wenn er sizeof(someThing)gleich ist PAGE_SIZE. Andernfalls wird ein Fehler beim Kompilieren erzeugt.

1. C11 Weg

Ab C11 können Sie verwenden static_assert(erfordert #include <assert.h>).

Verwendung:

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

2. Benutzerdefiniertes Makro

Wenn Sie nur einen Fehler beim Kompilieren erhalten möchten, wenn dies sizeof(something)nicht Ihren Erwartungen entspricht, können Sie das folgende Makro verwenden:

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

Verwendung:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

Dieser Artikel erklärt ausführlich, warum es funktioniert.

3. MS-spezifisch

Im Microsoft C ++ - Compiler können Sie das Makro C_ASSERT (erforderlich #include <windows.h>) verwenden, das einen ähnlichen Trick wie den in Abschnitt 2 beschriebenen verwendet.

Verwendung:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);
keine Ursache
quelle
4
...das ist verrückt. Warum ist dies nicht die akzeptierte Antwort, @Brad (OP)?
Ingenieur
Netter Verweis auf BUILD_BUG_ON.
Petr Vepřek
2
Das Makro funktioniert nicht in GNU gcc(getestet in Version 4.8.4) (Linux). Bei den ((void)sizeof(...es Fehler mit expected identifier or '(' before 'void'und expected ')' before 'sizeof'. Aber im Prinzip size_t x = (sizeof(...funktioniert es stattdessen wie beabsichtigt. Sie müssen das Ergebnis irgendwie "verwenden". Damit dies entweder innerhalb einer Funktion oder im globalen Bereich mehrmals aufgerufen werden extern char _BUILD_BUG_ON_ [ (sizeof(...) ];kann, kann so etwas wiederholt verwendet werden (keine Nebenwirkungen, eigentlich _BUILD_BUG_ON_nirgendwo referenzieren ).
JonBrave
Ich benutze statische Asserts schon viel länger als 2011 war ein Jahr.
Dan
1
@ Ingenieur schauen, Wahnsinn hat aufgehört;)
Bodo Thiesen
70

Gibt es überhaupt ein " sizeof" in einem Vorprozessor-Makro?

Nein. Die bedingten Anweisungen enthalten nur eingeschränkte bedingte Ausdrücke. sizeofist eines der Dinge, die nicht erlaubt sind.

Vorverarbeitungsanweisungen werden ausgewertet, bevor die Quelle analysiert wird (zumindest konzeptionell), sodass noch keine Typen oder Variablen vorhanden sind, um ihre Größe zu ermitteln.

Es gibt jedoch Techniken, um Zusicherungen zur Kompilierungszeit in C abzurufen (siehe beispielsweise diese Seite ).

James McNellis
quelle
Toller Artikel - clevere Lösung! Obwohl Sie admin müssen - sie haben die C-Syntax wirklich an ihre Grenzen gebracht, damit diese funktioniert! : -O
Brad
1
Es stellt sich heraus - wie der Artikel sogar sagt - ich baue gerade Linux-Kernel-Code - und es gibt bereits eine Definition im Kernel - BUILD_BUG_ON - wo der Kernel sie für Dinge wie: BUILD_BUG_ON (sizeof (char)! = 8)
Brad
2
@Brad BUILD_BUG_ON und andere generieren sicher falschen Code, der nicht kompiliert werden kann (und geben eine nicht offensichtliche Fehlermeldung in Bearbeitung). Nicht wirklich # if-Anweisung, daher können Sie z. B. einen darauf basierenden Codeblock nicht ausschließen.
Keltar
10

Ich weiß, dass es eine späte Antwort ist, aber um Mikes Version zu erweitern, hier ist eine Version, die wir verwenden und die keinen Speicher zuweist. Ich habe mir den Originalgrößencheck nicht ausgedacht, ich habe ihn vor Jahren im Internet gefunden und kann den Autor leider nicht referenzieren. Die anderen beiden sind nur Erweiterungen derselben Idee.

Da es sich um Typedefs handelt, wird nichts zugewiesen. Mit der __LINE__ im Namen ist es immer ein anderer Name, sodass er bei Bedarf kopiert und eingefügt werden kann. Dies funktioniert in MS Visual Studio C-Compilern und GCC Arm-Compilern. In CodeWarrior funktioniert es nicht. CW beschwert sich über eine Neudefinition und verwendet das Präprozessorkonstrukt __LINE__ nicht.

//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
quelle
Das funktioniert wirklich gut für ein Standard-C-Projekt ... Ich mag es!
Ashley Duncan
1
Dies sollte aufgrund der Nullzuweisung die richtige Antwort sein. Noch besser in eine Definition:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato
p__LINE__ erzeugt keinen eindeutigen Namen. Es erzeugt p__LINE__ als Variable. Sie benötigen ein Preproc-Makro und verwenden __CONCAT aus sys / cdefs.h.
Coroos
9

Ich weiß, dass dieser Thread wirklich alt ist, aber ...

Meine Lösung:

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

Solange dieser Ausdruck gleich Null ist, wird er gut kompiliert. Alles andere und es explodiert genau dort. Da die Variable extern ist, nimmt sie keinen Platz ein, und solange niemand darauf verweist (was sie nicht tun), verursacht sie keinen Linkfehler.

Nicht so flexibel wie das Assert-Makro, aber ich konnte das in meiner Version von GCC nicht kompilieren, und das wird ... und ich denke, es wird fast überall kompiliert.

Scott
quelle
6
Erfinde niemals deine eigenen Makros, beginnend mit zwei Unterstrichen. Dieser Weg liegt im Wahnsinn (auch bekannt als undefiniertes Verhalten ).
Jens
Es gibt eine Reihe von Beispielen auf dieser Seite pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast
funktioniert nicht beim kompilieren mit arm gcc compiler. gibt den erwarteten Fehler "Fehler: variabel geändert ' CHECK ' im Dateibereich"
Thunderbird
@Jens Sie haben Recht, aber dies ist buchstäblich kein Makro, sondern eine Variablendeklaration. Natürlich kann es Makros stören.
Melebius
4

Die vorhandenen Antworten zeigen nur, wie der Effekt von "Zusicherungen zur Kompilierungszeit" basierend auf der Größe eines Typs erzielt werden kann. Dies mag in diesem speziellen Fall den Anforderungen des OP entsprechen, aber es gibt andere Fälle, in denen Sie wirklich einen Präprozessor benötigen, der von der Größe eines Typs abhängig ist. So geht's:

Schreiben Sie sich ein kleines C-Programm wie:

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

Kompiliere das. Schreiben Sie ein Skript in Ihrer bevorzugten Skriptsprache, das das obige C-Programm ausführt und dessen Ausgabe erfasst. Verwenden Sie diese Ausgabe, um eine C-Header-Datei zu generieren. Wenn Sie beispielsweise Ruby verwenden, sieht es möglicherweise so aus:

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

Fügen Sie dann Ihrem Makefile oder einem anderen Build-Skript eine Regel hinzu, mit der das oben zu erstellende Skript ausgeführt wird sizes.h.

Fügen sizes.hSie ein, wo immer Sie Präprozessor-Bedingungen verwenden müssen, die auf Größen basieren.

Getan!

(Haben Sie jemals geschrieben ./configure && make, um ein Programm zu erstellen? Was configureSkripte tun, ist im Grunde genau wie oben ...)

Alex D.
quelle
Ähnlich verhält es sich, wenn Sie Tools wie "autoconf" verwenden.
Alexander Stohr
4

Was ist mit dem nächsten Makro:

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

Zum Beispiel sagt MSVC im Kommentar etwas wie:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits
Sergio
quelle
1
Dies ist keine Antwort auf die Frage, da Sie dies nicht in einer #ifPräprozessor-Direktive verwenden können.
cmaster
1

Nur als Referenz für diese Diskussion berichte ich, dass einige Compiler sizeof () ar Pre-Prozessor-Zeit erhalten.

Die Antwort von JamesMcNellis ist korrekt, aber einige Compiler gehen diese Einschränkung durch (dies verstößt wahrscheinlich gegen strenge Ansi c).

In diesem Fall verweise ich auf den IAR C-Compiler (wahrscheinlich der führende für professionelle Mikrocontroller / Embedded-Programmierung).

Graziano Governatori
quelle
Bist du dir da sicher? IAR behauptet, dass ihre Compiler den Normen ISO C90 und C99 entsprechen, die eine Bewertung zum sizeofZeitpunkt der Vorverarbeitung nicht zulassen . sizeofsollte nur als Kennung behandelt werden.
Keith Thompson
6
Im Jahr 1998 schrieb jemand in der Newsgroup comp.std.c: "Es war schön in den Tagen, als Dinge wie #if (sizeof(int) == 8)tatsächlich funktionierten (bei einigen Compilern)." Die Antwort: "Muss vor meiner Zeit gewesen sein.", War von Dennis Ritchie.
Keith Thompson
Entschuldigung für die verspätete Antwort ... Ja, ich bin sicher, ich habe Arbeitsbeispiele für Code, der für 8/16/32-Bit-Mikrocontroller, Renesas-Compiler (sowohl R8 als auch RX) kompiliert wurde.
Graziano Governatori
Eigentlich sollte es eine Option geben, um "strenge" ISO C
Graziano Governatori
Es ist keine Verletzung des Standards, solange der Standard dies nicht verbietet. dann würde ich es eine seltene und nicht standardmäßige Funktion nennen - daher werden Sie es in regelmäßigen Fällen vermeiden, um die Unabhängigkeit des Compilers und die Portabilität der Plattform zu gewährleisten.
Alexander Stohr
1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) könnte funktionieren


quelle
Dies ist eine interessante Lösung, funktioniert jedoch nur mit definierten Variablen, nicht mit Typen. Eine andere Lösung, die mit Typ, aber nicht mit Variablen funktioniert, wäre:#define SIZEOF_TYPE(x) (((x*)0) + 1)
Greydet
7
Es funktioniert nicht, weil Sie das Ergebnis immer noch nicht innerhalb einer #ifBedingung verwenden können. Es bietet keinen Vorteil gegenüber sizeof(x).
Interjay
1

In C11 wird ein _Static_assertSchlüsselwort hinzugefügt. Es kann verwendet werden wie:

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

In meinem portablen C ++ - Code ( http://www.starmessagesoftware.com/cpcclibrary/ ) wollte ich die Größe einiger meiner Strukturen oder Klassen sicher schützen.

Anstatt eine Möglichkeit für den Präprozessor zu finden, einen Fehler auszulösen (der nicht mit sizeof () funktioniert, wie hier angegeben), habe ich hier eine Lösung gefunden, die den Compiler veranlasst, einen Fehler auszulösen. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

Ich musste diesen Code anpassen, damit ein Fehler in meinem Compiler (xcode) ausgelöst wurde:

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
quelle
2
Sind Sie sicher, dass diese "-1" niemals als 0xFFFF… FF interpretiert werden, wodurch Ihr Programm den gesamten adressierbaren Speicher anfordert?
Anton Samsonov
0

Nach dem Ausprobieren der genannten Makros scheint dieses Fragment das gewünschte Ergebnis zu liefern (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);

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

Laufen 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 ist doch nicht die Antwort auf alles ...

Coroos
quelle
-10

Der sizeofOperator ist für den Präprozessor nicht verfügbar, aber Sie können sizeofzum Compiler übertragen und die Bedingung zur Laufzeit überprüfen:

#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);
}
Freeman
quelle
13
Wie verbessert es die bereits akzeptierte Antwort? Welchen Zweck hat die Definition compiler_size? Was versucht Ihr Beispiel zu zeigen?
Ugoren