Bagaimana cara menggunakan "sizeof" di makro praprosesor?

95

Apakah ada cara untuk menggunakan sizeofmakro preprocessor?

Misalnya, ada banyak sekali situasi selama bertahun-tahun di mana saya ingin melakukan sesuatu seperti:

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

Hal persis yang saya periksa di sini benar-benar dibuat-buat - intinya adalah, saya sering suka memasukkan jenis (ukuran atau keselarasan) pemeriksaan waktu kompilasi ini untuk mencegah seseorang memodifikasi struktur data yang dapat tidak sejajar atau ukuran hal-hal yang akan menghancurkan mereka.

Tak perlu dikatakan - sepertinya saya tidak dapat menggunakan a sizeofdengan cara yang dijelaskan di atas.

Brad
sumber
Inilah alasan pasti mengapa sistem build ada.
Šimon Tóth
3
Ini adalah alasan yang tepat mengapa perintah #error harus selalu dalam tanda kutip ganda (karakter tidak ditentukan konstan karena "tidak").
Jens
1
Halo @Brad. Mohon pertimbangkan untuk mengubah jawaban yang Anda terima menjadi jawaban lupakan, karena sementara itu, jawaban yang diterima saat ini agak usang.
Bodo Thiesen
@Soal_Ambarita
Brad

Jawaban:

69

Ada beberapa cara untuk melakukan ini. Cuplikan berikut tidak akan menghasilkan kode jika sizeof(someThing)sama dengan PAGE_SIZE; jika tidak, mereka akan menghasilkan kesalahan waktu kompilasi.

1. C11 cara

Dimulai dengan C11, Anda dapat menggunakan static_assert(membutuhkan #include <assert.h>).

Pemakaian:

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

2. Makro khusus

Jika Anda hanya ingin mendapatkan kesalahan waktu kompilasi ketika sizeof(something)tidak seperti yang Anda harapkan, Anda dapat menggunakan makro berikut:

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

Pemakaian:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

Artikel ini menjelaskan secara detail mengapa ini berhasil.

3. Khusus MS

Pada kompiler Microsoft C ++ Anda dapat menggunakan makro C_ASSERT (memerlukan #include <windows.h>), yang menggunakan trik yang mirip dengan yang dijelaskan di bagian 2.

Pemakaian:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);
lupakan
sumber
4
...itu gila. Mengapa ini bukan jawaban yang diterima, @Brad (OP)?
Insinyur
Referensi yang bagus untuk BUILD_BUG_ON.
Petr Vepřek
2
Makro tidak berfungsi di GNU gcc(diuji pada versi 4.8.4) (Linux). Saat ((void)sizeof(...itu kesalahan dengan expected identifier or '(' before 'void'dan expected ')' before 'sizeof'. Tapi pada prinsipnya size_t x = (sizeof(...malah bekerja sebagaimana mestinya. Anda harus "menggunakan" hasilnya. Untuk memungkinkan ini dipanggil beberapa kali baik di dalam fungsi atau di lingkup global, sesuatu seperti itu extern char _BUILD_BUG_ON_ [ (sizeof(...) ];dapat digunakan berulang kali (tanpa efek samping, sebenarnya tidak merujuk ke _BUILD_BUG_ON_mana pun).
JonBrave
Telah menggunakan pernyataan statis lebih lama dari tahun 2011 telah setahun.
Dan
1
@ Penampilan insinyur, kegilaan telah berhenti;)
Bodo Thiesen
70

Apakah ada cara untuk menggunakan " sizeof" dalam makro pra-prosesor?

Tidak. Petunjuk bersyarat mengambil serangkaian ekspresi bersyarat yang terbatas; sizeofadalah salah satu hal yang dilarang.

Arahan preprocessing dievaluasi sebelum sumber diurai (setidaknya secara konseptual), jadi tidak ada jenis atau variabel apa pun untuk mendapatkan ukurannya.

Namun, ada beberapa teknik untuk mendapatkan pernyataan waktu kompilasi dalam C (misalnya, lihat halaman ini ).

James McNellis
sumber
Artikel bagus - solusi cerdas! Meskipun Anda harus admin - mereka benar-benar mendorong sintaks C ke batasnya agar yang ini berfungsi! : -O
Brad
1
Ternyata - seperti yang dikatakan dalam artikel - Saya sedang membuat kode kernel Linux sekarang - dan sudah ada definisi di kernel - BUILD_BUG_ON - di mana kernel menggunakannya untuk hal-hal seperti: BUILD_BUG_ON (sizeof (char)! = 8)
Brad
2
@Brad BUILD_BUG_ON dan lainnya menghasilkan kode yang pasti salah yang akan gagal untuk dikompilasi (dan memberikan beberapa pesan kesalahan yang tidak jelas dalam proses). Tidak benar-benar pernyataan #if, jadi Anda tidak dapat, misalnya, mengecualikan blok kode berdasarkan ini.
keltar
10

Saya tahu ini jawaban yang terlambat, tetapi untuk menambahkan versi Mike, berikut adalah versi yang kami gunakan yang tidak mengalokasikan memori apa pun. Saya tidak menemukan cek ukuran asli, saya menemukannya di internet bertahun-tahun yang lalu dan sayangnya tidak dapat merujuk penulisnya. Dua lainnya hanyalah perluasan dari ide yang sama.

Karena mereka typedef's, tidak ada yang dialokasikan. Dengan __LINE__ di namanya, nama itu selalu berbeda sehingga dapat disalin dan ditempel jika diperlukan. Ini bekerja di kompiler MS Visual Studio C, dan kompiler GCC Arm. Ini tidak bekerja di CodeWarrior, CW mengeluh tentang redefinisi, tidak menggunakan konstruksi preprocessor __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
sumber
Ini benar-benar bekerja dengan sangat baik untuk proyek C standar ... Saya menyukainya!
Ashley Duncan
1
Yang ini harus menjadi jawaban yang benar karena alokasi nol. Bahkan lebih baik dalam mendefinisikan:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato
p__LINE__ tidak menghasilkan nama unik. Ini menghasilkan p__LINE__ sebagai variabel. Anda akan membutuhkan makro preproc dan menggunakan __CONCAT dari sys / cdefs.h.
Coroos
9

Saya tahu utas ini sangat tua tetapi ...

Solusi saya:

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

Selama ekspresi itu sama dengan nol, itu dikompilasi dengan baik. Ada lagi dan akan meledak di sana. Karena variabel di luar itu tidak akan memakan ruang, dan selama tidak ada yang mereferensikannya (yang tidak mereka lakukan) itu tidak akan menyebabkan kesalahan tautan.

Tidak sefleksibel makro pernyataan, tetapi saya tidak bisa membuatnya dikompilasi di versi GCC saya dan ini akan ... dan saya pikir itu akan dikompilasi di mana saja.

Scott
sumber
6
Jangan pernah menciptakan makro Anda sendiri yang dimulai dengan dua garis bawah. Jalan ini terletak pada kegilaan (alias perilaku tidak terdefinisi ).
Jens
Ada banyak contoh yang tercantum di halaman ini pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast
tidak berfungsi saat dikompilasi dengan compiler arm gcc. memberikan kesalahan yang diharapkan "kesalahan: ' PERIKSA ' yang dimodifikasi secara bervariasi pada cakupan file"
thunderbird
@ Jens Anda benar tetapi ini secara harfiah bukan makro, ini adalah deklarasi variabel. Tentu saja, ini dapat mengganggu makro.
Melebius
4

Jawaban yang ada hanya menunjukkan bagaimana mencapai efek "pernyataan waktu kompilasi" berdasarkan ukuran tipe. Itu mungkin memenuhi kebutuhan OP dalam kasus khusus ini, tetapi ada kasus lain di mana Anda benar-benar membutuhkan praprosesor bersyarat berdasarkan ukuran suatu tipe. Berikut cara melakukannya:

Tulis sendiri program C kecil seperti:

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

Kompilasi itu. Tulis skrip dalam bahasa skrip favorit Anda, yang menjalankan program C di atas dan menangkap hasilnya. Gunakan output tersebut untuk menghasilkan file header C. Misalnya, jika Anda menggunakan Ruby, akan terlihat seperti ini:

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

Kemudian tambahkan aturan ke Makefile Anda atau skrip build lainnya, yang akan membuatnya menjalankan skrip di atas untuk membangun sizes.h.

Sertakan di sizes.hmana pun Anda perlu menggunakan praprosesor bersyarat berdasarkan ukuran.

Selesai!

(Pernahkah Anda mengetik ./configure && makeuntuk membangun program? configureSkrip apa yang pada dasarnya sama seperti di atas ...)

Alex D
sumber
Ini sama saja jika Anda menggunakan alat seperti "autoconf".
Alexander Stohr
4

Bagaimana dengan makro berikutnya:

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

Misalnya dalam komentar MSVC mengatakan sesuatu seperti:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits
Sergio
sumber
1
Ini bukanlah jawaban atas pertanyaan karena Anda tidak dapat menggunakan ini dalam #ifarahan preprocessor.
cmaster
1

Sekadar referensi untuk pembahasan kali ini, saya melaporkan bahwa beberapa compiler mendapatkan sizeof () ar waktu pra-prosesor.

Jawaban JamesMcNellis benar, tetapi beberapa kompiler melewati batasan ini (ini mungkin melanggar ansi c ketat).

Untuk kasus ini, saya merujuk ke IAR C-compiler (mungkin yang terkemuka untuk mikrokontroler profesional / pemrograman tertanam).

graziano Governatori
sumber
Apa kamu yakin akan hal itu? IAR mengklaim bahwa kompilernya sesuai dengan standar ISO C90 dan C99, yang tidak mengizinkan evaluasi sizeofpada waktu praproses. sizeofharus diperlakukan hanya sebagai pengenal.
Keith Thompson
6
Pada tahun 1998, seseorang di newsgroup comp.std.c menulis: "Senang sekali kembali pada hari-hari ketika hal-hal seperti itu #if (sizeof(int) == 8)benar - benar bekerja (pada beberapa kompiler)." Tanggapan: "Pasti sebelum waktu saya.", Berasal dari Dennis Ritchie.
Keith Thompson
Maaf untuk balasan terlambat ... Ya, saya yakin, saya memiliki contoh kode yang dikompilasi untuk mikrokontroler 8/16/32 bit, kompiler Renesas (baik R8 dan RX).
graziano Governatori
Sebenarnya, harus ada beberapa opsi untuk mewajibkan ISO C yang "ketat"
graziano Governori
bukan merupakan pelanggaran standar selama standar tidak melarangnya. maka saya akan menyebutnya fitur langka dan non-standar - sehingga Anda akan menghindarinya dalam kasus biasa demi menjaga independensi compiler & portabilitas platform.
Alexander Stohr
1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) mungkin berhasil


sumber
Ini adalah solusi yang menarik namun hanya berfungsi dengan variabel yang ditentukan, bukan dengan tipe. Solusi lain yang bekerja dengan tipe tetapi tidak dengan variabel adalah:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet
7
Itu tidak berfungsi karena Anda masih tidak dapat menggunakan hasilnya dalam suatu #ifkondisi. Ini tidak memberikan keuntungan lebih sizeof(x).
interjay
1

Dalam C11 _Static_assertkata kunci ditambahkan. Ini bisa digunakan seperti:

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

Dalam kode c ++ portabel saya ( http://www.starmessagesoftware.com/cpcclibrary/ ) ingin memberi perlindungan yang aman pada ukuran beberapa struct atau kelas saya.

Alih-alih menemukan cara bagi preprocessor untuk melakukan kesalahan (yang tidak dapat bekerja dengan sizeof () seperti yang dinyatakan di sini), saya menemukan solusi di sini yang menyebabkan kompiler melakukan kesalahan. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

Saya harus menyesuaikan kode itu untuk membuatnya membuat kesalahan pada kompiler saya (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
sumber
2
Apakah Anda yakin “−1” itu tidak akan pernah ditafsirkan sebagai 0xFFFF… FF, menyebabkan program Anda meminta semua memori yang dapat dialamatkan?
Anton Samsonov
0

Setelah mencoba makro yang disebutkan, fragmen ini tampaknya menghasilkan hasil yang diinginkan ( 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);

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

Berjalan 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 bukanlah jawaban untuk segalanya ...

Coroos
sumber
0

Untuk memeriksa pada waktu kompilasi ukuran struktur data terhadap batasannya, saya telah menggunakan trik ini.

#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

Jika ukuran x lebih besar atau sama dari batasnya MAX_SIZEOF_X, maka gcc akan mengeluh dengan kesalahan 'ukuran larik terlalu besar'. VC ++ akan mengeluarkan kesalahan C2148 ('ukuran total larik tidak boleh melebihi 0x7fffffff byte') atau C4266 'tidak dapat mengalokasikan larik dengan ukuran konstan 0'.

Kedua definisi tersebut dibutuhkan karena gcc akan memungkinkan array berukuran nol untuk didefinisikan dengan cara ini (sizeof x - n).

Miguel de Reyna
sumber
-10

The sizeofOperator tidak tersedia untuk preprocessor, tetapi Anda dapat mentransfer sizeofke compiler dan memeriksa kondisi di 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);
}
Warga kehormatan
sumber
13
Bagaimana cara meningkatkan jawaban yang sudah diterima? Apa gunanya mendefinisikan compiler_size? Apa yang coba ditunjukkan oleh teladan Anda?
ugoren