Jak mogę użyć „sizeof” w makrze preprocesora?

95

Czy istnieje sposób na użycie sizeofmakra preprocesora?

Na przykład przez lata było mnóstwo sytuacji, w których chciałem zrobić coś takiego:

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

Dokładna rzecz, którą tutaj sprawdzam, jest całkowicie wymyślona - chodzi o to, że często lubię wprowadzać tego typu kontrole (rozmiar lub wyrównanie) w czasie kompilacji, aby chronić przed kimś modyfikującym strukturę danych, która mogłaby źle dopasować lub zmienić rozmiar rzeczy, które mogłyby je złamać.

Nie trzeba dodawać - nie wydaje mi się, żebym mógł używać sizeofw sposób opisany powyżej.

Ćwiek
źródło
To jest dokładny powód, dla którego istnieją systemy kompilacji.
Šimon Tóth
3
To jest dokładny powód, dla którego dyrektywy #error powinny zawsze znajdować się w podwójnych cudzysłowach (niezakończona stała znakowa z powodu „nie”).
Jens
1
Witaj @Brad. Prosimy o rozważenie zmiany zaakceptowanej odpowiedzi na odpowiedź „nieważne”, ponieważ w międzyczasie aktualnie akceptowana odpowiedź stała się nieco nieaktualna.
Bodo Thiesen
@BodoThiesen Gotowe.
Brad

Odpowiedzi:

71

Można to zrobić na kilka sposobów. Następujące fragmenty nie wygenerują żadnego kodu, jeśli są sizeof(someThing)równe PAGE_SIZE; w przeciwnym razie spowodują błąd w czasie kompilacji.

1. C11 sposób

Począwszy od C11 możesz użyć static_assert(wymagania #include <assert.h>).

Stosowanie:

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

2. Makro niestandardowe

Jeśli chcesz po prostu otrzymać błąd kompilacji, który sizeof(something)nie jest tym, czego oczekujesz, możesz użyć następującego makra:

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

Stosowanie:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

W tym artykule szczegółowo wyjaśniono, dlaczego to działa.

3. Specyficzne dla MS

Na kompilatorze Microsoft C ++ możesz użyć makra C_ASSERT (wymaga #include <windows.h>), które wykorzystuje sztuczkę podobną do tej opisanej w sekcji 2.

Stosowanie:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);
nieważne
źródło
4
...To jest szalone. Dlaczego nie jest to akceptowana odpowiedź, @Brad (OP)?
Inżynier
Niezłe odniesienie do BUILD_BUG_ON.
Petr Vepřek
2
Makro nie działa w gccsystemie GNU (testowane w wersji 4.8.4) (Linux). Na ((void)sizeof(...to błędy z expected identifier or '(' before 'void'i expected ')' before 'sizeof'. Ale w zasadzie size_t x = (sizeof(...działa zgodnie z przeznaczeniem. Trzeba jakoś „wykorzystać” wynik. Aby umożliwić wielokrotne wywoływanie tego w funkcji lub w zasięgu globalnym, coś takiego extern char _BUILD_BUG_ON_ [ (sizeof(...) ];może być używane wielokrotnie (bez skutków ubocznych, _BUILD_BUG_ON_nigdzie nie odwołuj się).
JonBrave
Używam potwierdzeń statycznych znacznie dłużej niż rok 2011.
Dan
1
@Engineer look, szaleństwo ustało;)
Bodo Thiesen
71

Czy w ogóle jest użycie znaku „ sizeof” w makrze preprocesora?

Nie. Dyrektywy warunkowe przyjmują ograniczony zestaw wyrażeń warunkowych; sizeofjest jedną z niedozwolonych rzeczy.

Dyrektywy przetwarzania wstępnego są oceniane przed przeanalizowaniem źródła (przynajmniej koncepcyjnie), więc nie ma jeszcze żadnych typów ani zmiennych, aby uzyskać ich rozmiar.

Istnieją jednak techniki uzyskiwania asercji w czasie kompilacji w języku C (na przykład zobacz tę stronę ).

James McNellis
źródło
Świetny artykuł - sprytne rozwiązanie! Chociaż musisz administrować - naprawdę przesunęli składnię C do granic możliwości, aby ta działała! : -O
Brad
1
Okazuje się - jak nawet mówi artykuł - właśnie buduję kod jądra Linuksa - i jest już zdefiniowane w jądrze - BUILD_BUG_ON - gdzie jądro używa go do rzeczy takich jak: BUILD_BUG_ON (sizeof (char)! = 8)
Brad
2
@Brad BUILD_BUG_ON i inni generują z pewnością niepoprawny kod, który nie uda się skompilować (i poda jakiś nieoczywisty komunikat o błędzie w trakcie). Niezupełnie #if, więc nie możesz np. Wykluczyć bloku kodu opartego na tym.
keltar
10

Wiem, że to późna odpowiedź, ale aby dodać do wersji Mike'a, oto wersja, której używamy, która nie przydziela żadnej pamięci. Nie wymyśliłem oryginalnego sprawdzenia rozmiaru, znalazłem go w Internecie lata temu i niestety nie mogę się odwołać do autora. Pozostałe dwa to tylko rozszerzenia tego samego pomysłu.

Ponieważ są typedef, nic nie jest przydzielane. Z __LINE__ w nazwie, zawsze jest to inna nazwa, więc można ją skopiować i wkleić w razie potrzeby. Działa to w kompilatorach MS Visual Studio C i kompilatorach GCC Arm. Nie działa w CodeWarrior, CW narzeka na redefinicję, nie korzysta z konstrukcji preprocesora __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];
Paweł
źródło
To naprawdę działa naprawdę dobrze w standardowym projekcie C ... Podoba mi się!
Ashley Duncan,
1
Ta odpowiedź powinna być poprawna ze względu na zerową alokację. Jeszcze lepiej do określenia:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato,
p__LINE__ nie tworzy unikalnej nazwy. Daje p__LINE__ jako zmienną. Potrzebujesz makra preproc i użyj __CONCAT z sys / cdefs.h.
Coroos
9

Wiem, że ten wątek jest naprawdę stary, ale ...

Moje rozwiązanie:

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

Dopóki to wyrażenie jest równe zero, kompiluje się dobrze. Cokolwiek innego i to właśnie tam wybucha. Ponieważ zmienna jest umieszczona na zewnątrz, nie zajmie miejsca i dopóki nikt się do niej nie odwoła (a tego nie zrobi), nie spowoduje to błędu łącza.

Nie tak elastyczne jak makro assert, ale nie mogłem go skompilować w mojej wersji GCC, a to ... i myślę, że skompiluje się prawie wszędzie.

Scott
źródło
6
Nigdy nie wymyślaj własnych makr zaczynających się od dwóch podkreśleń. Ta ścieżka leży w szaleństwie (czyli niezdefiniowanym zachowaniu ).
Jens
Na tej stronie znajduje się kilka przykładów. Pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast
nie działa po skompilowaniu za pomocą kompilatora arm gcc. daje oczekiwany błąd „błąd: zmiennie zmodyfikowany ' CHECK ' w zakresie pliku”
thunderbird
@Jens Masz rację, ale to nie jest dosłownie makro, to deklaracja zmiennej. Oczywiście może to zakłócać działanie makr.
Melebius
4

Istniejące odpowiedzi pokazują tylko, jak osiągnąć efekt „asercji w czasie kompilacji” w oparciu o rozmiar typu. Może to zaspokoić potrzeby PO w tym konkretnym przypadku, ale są inne przypadki, w których naprawdę potrzebujesz preprocesora warunkowego w oparciu o rozmiar typu. Oto jak to zrobić:

Napisz sobie mały program w C, taki jak:

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

Skompiluj to. Napisz skrypt w swoim ulubionym języku skryptowym, który uruchamia powyższy program w C i przechwytuje jego dane wyjściowe. Użyj tego wyjścia, aby wygenerować plik nagłówkowy C. Na przykład, jeśli używasz Rubiego, może to wyglądać tak:

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

Następnie dodaj regułę do swojego Makefile lub innego skryptu budującego, co spowoduje uruchomienie powyższego skryptu w celu zbudowania sizes.h.

Uwzględnij sizes.hwszędzie tam, gdzie potrzebujesz użyć warunków warunkowych preprocesora na podstawie rozmiarów.

Gotowe!

(Czy kiedykolwiek pisałeś, ./configure && makeaby zbudować program? Jakie configureskrypty robią, jest w zasadzie takie samo jak powyższe ...)

Alex D.
źródło
jest podobnie, gdy używasz narzędzi takich jak „autoconf”.
Alexander Stohr
4

A co z następnym makrem:

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

Na przykład w komentarzu MSVC mówi coś takiego:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits
Sergio
źródło
1
To nie jest odpowiedź na pytanie, ponieważ nie można tego użyć w #ifdyrektywie preprocesora.
cmaster
1

Jako odniesienie do tej dyskusji, zgłaszam, że niektóre kompilatory uzyskują sizeof () ar czasu preprocesora.

Odpowiedź Jamesa McNellisa jest poprawna, ale niektórzy kompilatorzy przechodzą przez to ograniczenie (prawdopodobnie narusza to ścisłą ansi c).

W tym przypadku odnoszę się do kompilatora IAR C (prawdopodobnie wiodącego dla profesjonalnego mikrokontrolera / programowania wbudowanego).

graziano Governatori
źródło
Czy jesteś tego pewien? IAR twierdzi, że ich kompilatory są zgodne z normami ISO C90 i C99, które nie pozwalają na ocenę sizeofw czasie wstępnego przetwarzania. sizeofnależy traktować jako zwykły identyfikator.
Keith Thompson
6
W 1998 roku ktoś z grupy dyskusyjnej comp.std.c napisał: „Było miło w czasach, kiedy takie rzeczy #if (sizeof(int) == 8)faktycznie działały (na niektórych kompilatorach)”. Odpowiedź: „To musiało być przed moimi czasami”, pochodziła od Dennisa Ritchiego.
Keith Thompson
Przepraszam za spóźnioną odpowiedź ... Tak, jestem pewien, mam działające przykłady kodu skompilowanego dla mikrokontrolerów 8/16/32 bitowych, kompilatorów Renesas (zarówno R8, jak i RX).
graziano Governatori
Właściwie powinna istnieć opcja wymagająca „ścisłego” ISO C
graziano Governatori
nie jest to naruszenie normy, o ile norma tego nie zabrania. nazwałbym to rzadką i niestandardową funkcją - w ten sposób unikniesz jej w zwykłych przypadkach, aby zachować niezależność kompilatora i przenośność platformy.
Alexander Stohr
1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) może działać


źródło
To ciekawe rozwiązanie, ale działa tylko ze zdefiniowanymi zmiennymi, a nie z typami. Innym rozwiązaniem, które działa z typem, ale nie ze zmiennymi, byłoby:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet
7
To nie działa, ponieważ nadal nie możesz użyć jego wyniku w #ifwarunku. Nie zapewnia żadnych korzyści sizeof(x).
interjay
1

W C11 _Static_assertdodano słowo kluczowe. Może być używany jako:

_Static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size")
cagatayo
źródło
0

W moim przenośnym kodzie C ++ ( http://www.starmessagesoftware.com/cpcclibrary/ ) chciałem zabezpieczyć rozmiary niektórych moich struktur lub klas.

Zamiast znaleźć sposób, w jaki preprocesor zgłosi błąd (który nie może działać z sizeof (), jak podano tutaj), znalazłem tutaj rozwiązanie, które powoduje, że kompilator zgłasza błąd. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

Musiałem dostosować ten kod, aby generował błąd w moim kompilatorze (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];
};
Mikrofon
źródło
2
Czy na pewno te „-1” nigdy nie zostaną zinterpretowane jako 0xFFFF… FF, powodując, że program zażąda całej adresowalnej pamięci?
Anton Samsonov
0

Po wypróbowaniu wspomnianych makr ten fragment wydaje się dawać pożądany rezultat ( 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);

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

Bieganie 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 nie jest jednak odpowiedzią na wszystko ...

Coroos
źródło
0

Aby sprawdzić w czasie kompilacji rozmiar struktur danych pod kątem ich ograniczeń, użyłem tej sztuczki.

#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

Jeśli rozmiar x jest większy lub równy limitowi MAX_SIZEOF_X, to gcc będzie narzekać z błędem „rozmiar tablicy jest za duży”. VC ++ wygeneruje błąd C2148 („całkowity rozmiar tablicy nie może przekraczać 0x7fffffff bajtów”) lub C4266 „nie może przydzielić tablicy o stałym rozmiarze 0”.

Te dwie definicje są potrzebne, ponieważ gcc pozwoli na zdefiniowanie w ten sposób tablicy o rozmiarze zerowym (sizeof x - n).

Miguel de Reyna
źródło
-10

sizeofOperator nie jest dostępny dla preprocesora, ale można przenieść sizeofdo kompilatora i sprawdzenie stanu w czasie pracy:

#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);
}
Obywatel
źródło
13
Jak to się poprawia w stosunku do już zaakceptowanej odpowiedzi? Jakiemu celowi compiler_sizesłuży definiowanie ? Co twój przykład próbuje pokazać?
ugoren