ฉันจะใช้“ sizeof” ในมาโครตัวประมวลผลล่วงหน้าได้อย่างไร


96

มีวิธีใดบ้างในการใช้sizeofมาโครตัวประมวลผลล่วงหน้า

ตัวอย่างเช่นมีสถานการณ์มากมายในช่วงหลายปีที่ฉันต้องการทำสิ่งต่างๆเช่น:

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

สิ่งที่แน่นอนที่ฉันตรวจสอบที่นี่ถูกสร้างขึ้นอย่างสมบูรณ์ - ประเด็นคือฉันมักจะชอบใส่การตรวจสอบเวลาคอมไพล์ (ขนาดหรือการจัดตำแหน่ง) ประเภทนี้เพื่อป้องกันคนที่แก้ไขโครงสร้างข้อมูลซึ่งอาจไม่ตรงแนวหรือใหม่ ขนาดของสิ่งที่จะทำลายพวกเขา

ไม่จำเป็นต้องพูด - ดูเหมือนว่าฉันจะไม่สามารถใช้ a sizeofในลักษณะที่อธิบายไว้ข้างต้นได้


นี่คือเหตุผลที่แท้จริงว่าทำไมระบบการสร้างจึงมีอยู่
ŠimonTóth

3
นี่คือเหตุผลที่แท้จริงว่าทำไมคำสั่ง #error ควรอยู่ในเครื่องหมายคำพูดคู่เสมอ (ค่าคงที่ของอักขระที่ไม่สิ้นสุดเนื่องจาก "ไม่")
Jens

1
สวัสดี @ แบรด. โปรดพิจารณาเปลี่ยนคำตอบที่คุณยอมรับเป็นคำตอบของไม่เป็นไรเนื่องจากในช่วงเวลานั้นคำตอบที่ยอมรับในปัจจุบันล้าสมัยไปเล็กน้อย
Bodo Thiesen

@BodoThiesen เสร็จแล้ว.
Brad

คำตอบ:


72

มีหลายวิธีในการดำเนินการนี้ ต่อไปนี้จะผลิตตัวอย่างรหัสถ้าsizeof(someThing)เท่ากับPAGE_SIZE; มิฉะนั้นจะทำให้เกิดข้อผิดพลาดเวลาคอมไพล์

1. วิธี C11

เริ่มต้นด้วย C11 คุณสามารถใช้static_assert(ต้องการ#include <assert.h>)

การใช้งาน:

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

2. มาโครที่กำหนดเอง

หากคุณต้องการรับข้อผิดพลาดเวลาคอมไพล์เมื่อsizeof(something)ไม่ใช่สิ่งที่คุณคาดหวังคุณสามารถใช้มาโครต่อไปนี้:

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

การใช้งาน:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

บทความนี้อธิบายรายละเอียดว่าเหตุใดจึงใช้งานได้

3. MS เฉพาะ

บนคอมไพเลอร์ Microsoft C ++ คุณสามารถใช้มาโครC_ASSERT (ต้องใช้#include <windows.h>) ซึ่งใช้เคล็ดลับคล้ายกับที่อธิบายไว้ในส่วนที่ 2

การใช้งาน:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);

4
... มันบ้า เหตุใดจึงไม่เป็นคำตอบที่ยอมรับ @Brad (OP)?
วิศวกร

เป็นข้อมูลอ้างอิงที่ดีสำหรับ BUILD_BUG_ON
Petr Vepřek

2
มาโครไม่ทำงานใน GNU gcc(ทดสอบที่เวอร์ชัน 4.8.4) (Linux) ที่((void)sizeof(...ข้อผิดพลาดกับexpected identifier or '(' before 'void'และexpected ')' before 'sizeof'. แต่โดยหลักการแล้วsize_t x = (sizeof(...แทนที่จะได้ผลตามที่ตั้งใจไว้ คุณต้อง "ใช้" ผลลัพธ์อย่างใด เพื่อให้สามารถเรียกสิ่งนี้ได้หลายครั้งไม่ว่าจะภายในฟังก์ชันหรือในขอบเขตทั่วโลกบางสิ่งเช่นextern char _BUILD_BUG_ON_ [ (sizeof(...) ];สามารถใช้ซ้ำ ๆ ได้ (ไม่มีผลข้างเคียงไม่ต้องอ้างอิง_BUILD_BUG_ON_ที่ใดก็ได้)
JonBrave

ใช้การยืนยันแบบคงที่มานานกว่าปี 2554 เป็นปีแล้ว
แดน

1
@ หน้าตาของวิศวกรความวิกลจริตหยุดลงแล้ว;)
Bodo Thiesen

71

มีการใช้ " sizeof" ในมาโครก่อนโปรเซสเซอร์หรือไม่

ไม่คำสั่งเงื่อนไขใช้ชุดนิพจน์เงื่อนไขที่ จำกัด sizeofเป็นหนึ่งในสิ่งที่ไม่อนุญาต

คำสั่งก่อนการประมวลผลจะได้รับการประเมินก่อนที่จะแยกวิเคราะห์แหล่งที่มา (อย่างน้อยก็ตามแนวคิด) ดังนั้นจึงยังไม่มีประเภทหรือตัวแปรใด ๆ ที่จะได้ขนาด

อย่างไรก็ตามมีเทคนิคในการรับการยืนยันเวลาคอมไพล์ในภาษา C (ตัวอย่างเช่นดูหน้านี้ )


บทความยอดเยี่ยม - วิธีแก้ปัญหาที่ชาญฉลาด! แม้ว่าคุณจะต้องเป็นผู้ดูแลระบบ แต่พวกเขาก็ผลักดันให้ไวยากรณ์ C ถูก จำกัด เพื่อให้ใช้งานได้! : -O
Brad

1
ปรากฎว่า - ตามที่บทความกล่าวไว้ - ตอนนี้ฉันกำลังสร้างโค้ดเคอร์เนลลินุกซ์ - และมีการกำหนดอยู่แล้วในเคอร์เนล - BUILD_BUG_ON - โดยที่เคอร์เนลใช้สำหรับสิ่งต่างๆเช่น BUILD_BUG_ON (sizeof (char)! = 8)
Brad

2
@Brad BUILD_BUG_ON และคนอื่น ๆ ที่สร้างรหัสที่ไม่ถูกต้องซึ่งจะไม่สามารถรวบรวมได้ (และให้ข้อความแสดงข้อผิดพลาดที่ไม่ชัดเจนในกระบวนการ) ไม่ใช่คำสั่ง #if จริงๆดังนั้นคุณจึงไม่สามารถยกเว้นบล็อกของรหัสตามนี้
keltar

10

ฉันรู้ว่ามันเป็นคำตอบที่ล่าช้า แต่หากต้องการเพิ่มในเวอร์ชันของ Mike นี่คือเวอร์ชันที่เราใช้ซึ่งไม่ได้จัดสรรหน่วยความจำใด ๆ ฉันไม่ได้ตรวจสอบขนาดต้นฉบับฉันพบในอินเทอร์เน็ตเมื่อหลายปีก่อนและไม่สามารถอ้างอิงผู้แต่งได้ อีกสองรายการเป็นเพียงส่วนขยายของแนวคิดเดียวกัน

เนื่องจากเป็นประเภท typedef จึงไม่มีการจัดสรรอะไร ด้วยชื่อ __LINE__ จึงเป็นชื่อที่แตกต่างกันเสมอเพื่อให้สามารถคัดลอกและวางได้ตามต้องการ สิ่งนี้ใช้ได้กับคอมไพเลอร์ MS Visual Studio C และคอมไพเลอร์ GCC Arm มันใช้ไม่ได้ใน CodeWarrior CW บ่นเกี่ยวกับการกำหนดนิยามใหม่ไม่ได้ใช้ประโยชน์จากโครงสร้างตัวประมวลผลล่วงหน้า __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];

นี่ใช้งานได้ดีจริงๆสำหรับโครงการ C มาตรฐาน ... ฉันชอบมัน!
Ashley Duncan

1
อันนี้น่าจะเป็นคำตอบที่ถูกต้องเนื่องจากการจัดสรรเป็นศูนย์ ยิ่งไปกว่านั้นในการกำหนด:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato

p__LINE__ ไม่มีชื่อเฉพาะ สร้าง p__LINE__ เป็นตัวแปร คุณจะต้องมีมาโครพร็อคและใช้ __CONCAT จาก sys / cdefs.h
Coroos

9

ฉันรู้ว่ากระทู้นี้เก่าจริงๆ แต่ ...

วิธีแก้ปัญหาของฉัน:

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

ตราบใดที่นิพจน์นั้นมีค่าเท่ากับศูนย์ก็จะคอมไพล์ได้ดี สิ่งอื่นใดและมันก็ระเบิดขึ้นที่นั่น เนื่องจากตัวแปรอยู่ภายนอกมันจะไม่ใช้พื้นที่และตราบใดที่ไม่มีใครอ้างถึง (ซึ่งจะไม่ทำ) ก็จะไม่ทำให้เกิดข้อผิดพลาดในการเชื่อมโยง

ไม่ยืดหยุ่นเท่ามาโครยืนยัน แต่ฉันไม่สามารถรวบรวมสิ่งนั้นใน GCC เวอร์ชันของฉันได้และสิ่งนี้จะ ... และฉันคิดว่ามันจะรวบรวมได้ทุกที่


6
อย่าประดิษฐ์มาโครของคุณเองโดยเริ่มต้นด้วยขีดล่างสองอัน เส้นทางนี้เป็นความบ้าคลั่ง (หรือที่เรียกว่าพฤติกรรมที่ไม่ได้กำหนด )
Jens

มีตัวอย่างมากมายที่ระบุไว้ในหน้านี้pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast

ไม่ทำงานเมื่อคอมไพล์ด้วยคอมไพเลอร์ arm gcc ให้ข้อผิดพลาดที่คาดไว้ "error: modified" CHECK "at file scope"
thunderbird

@Jens คุณพูดถูก แต่นี่ไม่ใช่มาโคร แต่เป็นการประกาศตัวแปร แน่นอนมันอาจรบกวนมาโคร
Melebius

4

คำตอบที่มีอยู่จะแสดงวิธีการบรรลุผลของ "การยืนยันเวลารวบรวม" ตามขนาดของประเภท ซึ่งอาจตอบสนองความต้องการของ OP ในกรณีนี้ แต่มีบางกรณีที่คุณต้องการเงื่อนไขตัวประมวลผลล่วงหน้าตามขนาดของประเภท วิธีการทำมีดังนี้

เขียนโปรแกรม C ตัวเองเช่น:

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

รวบรวมสิ่งนั้น เขียนสคริปต์ด้วยภาษาสคริปต์ที่คุณชื่นชอบซึ่งเรียกใช้โปรแกรม C ด้านบนและรวบรวมผลลัพธ์ ใช้เอาต์พุตนั้นเพื่อสร้างไฟล์ส่วนหัว C ตัวอย่างเช่นหากคุณใช้ Ruby อาจมีลักษณะดังนี้:

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

จากนั้นเพิ่มกฎใน Makefile ของคุณหรือสคริปต์บิลด์อื่น ๆ ซึ่งจะทำให้เรียกใช้สคริปต์ด้านบนเพื่อสร้าง sizes.hของคุณซึ่งจะทำให้เรียกใช้สคริปต์ข้างต้นเพื่อสร้าง

รวมsizes.hทุกที่ที่คุณต้องการใช้เงื่อนไขของตัวประมวลผลล่วงหน้าตามขนาด

เสร็จแล้ว!

(คุณเคยพิมพ์./configure && makeเพื่อสร้างโปรแกรมหรือไม่configureสคริปต์อะไรทำก็เหมือนกับข้างบน ... )


มันเป็นสิ่งที่คล้ายกันเมื่อคุณใช้เครื่องมือเช่น "autoconf"
Alexander Stohr

4

สิ่งที่เกี่ยวกับมาโครถัดไป:

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

ตัวอย่างเช่นในความคิดเห็น MSVC บอกบางสิ่งเช่น:

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

1
นี่ไม่ใช่คำตอบสำหรับคำถามเนื่องจากคุณไม่สามารถใช้สิ่งนี้ใน#ifคำสั่งพรีโปรเซสเซอร์
cmaster - คืนสถานะโมนิกา

1

เพื่อเป็นข้อมูลอ้างอิงสำหรับการสนทนานี้ฉันรายงานว่าคอมไพเลอร์บางตัวได้รับเวลาก่อนตัวประมวลผล sizeof () ar

คำตอบ JamesMcNellis ถูกต้อง แต่คอมไพเลอร์บางตัวต้องผ่านข้อ จำกัด นี้ (ซึ่งอาจเป็นการละเมิด ansi c ที่เข้มงวด)

ในกรณีนี้ฉันอ้างถึง IAR C-compiler (อาจเป็นหนึ่งในผู้นำสำหรับไมโครคอนโทรลเลอร์ระดับมืออาชีพ / โปรแกรมฝังตัว)


คุณแน่ใจหรือไม่? IAR อ้างว่าคอมไพเลอร์ของตนเป็นไปตามมาตรฐาน ISO C90 และ C99 ซึ่งไม่อนุญาตให้มีการประเมินผลsizeofในช่วงก่อนการประมวลผล sizeofควรถือว่าเป็นเพียงตัวระบุ
Keith Thompson

6
ในปี 1998 มีคนในกลุ่มข่าว comp.std.c เขียนว่า "มันเป็นเรื่องดีในสมัยที่สิ่งต่างๆเช่น#if (sizeof(int) == 8)ใช้งานได้จริง (ในคอมไพเลอร์บางตัว)" คำตอบ: "ต้องมาก่อนเวลาของฉัน" มาจากเดนนิสริตชี่
Keith Thompson

ขออภัยที่ตอบช้า ... ใช่ฉันแน่ใจว่าฉันมีตัวอย่างการทำงานของโค้ดที่คอมไพล์สำหรับไมโครคอนโทรลเลอร์ 8/16/32 บิตคอมไพเลอร์ Renesas (ทั้ง R8 และ RX)
graziano Governoratori

จริงๆแล้วควรมีตัวเลือกบางอย่างสำหรับการกำหนด ISO C ที่ "เข้มงวด"
graziano Governoratori

ไม่เป็นการละเมิดมาตรฐานตราบใดที่มาตรฐานไม่ได้ห้ามไว้ ฉันจะเรียกมันว่าเป็นคุณสมบัติที่หายากและไม่ได้มาตรฐานดังนั้นคุณจะหลีกเลี่ยงมันในกรณีปกติเพื่อรักษาความเป็นอิสระของคอมไพเลอร์และการพกพาแพลตฟอร์ม
Alexander Stohr

1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) อาจใช้งานได้


นี่เป็นวิธีการแก้ปัญหาที่น่าสนใจ แต่ใช้ได้กับตัวแปรที่กำหนดเท่านั้นไม่ใช่กับประเภท อีกวิธีหนึ่งที่ใช้ได้กับประเภท แต่ไม่ใช้กับตัวแปรคือ#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet

7
ไม่ได้ผลเนื่องจากคุณยังไม่สามารถใช้ผลลัพธ์ภายใน#ifเงื่อนไขได้ ไม่ให้ประโยชน์ใดsizeof(x)
interjay


0

ในรหัส c ++ แบบพกพาของฉัน ( http://www.starmessagesoftware.com/cpcclibrary/ ) ต้องการป้องกันความปลอดภัยในขนาดของโครงสร้างหรือคลาสบางส่วนของฉัน

แทนที่จะหาวิธีให้ตัวประมวลผลก่อนส่งข้อผิดพลาด (ซึ่งไม่สามารถทำงานกับ sizeof () ตามที่ระบุไว้ที่นี่) ฉันพบวิธีแก้ปัญหาที่นี่ซึ่งทำให้คอมไพเลอร์แสดงข้อผิดพลาด http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

ฉันต้องปรับรหัสนั้นเพื่อให้เกิดข้อผิดพลาดในคอมไพเลอร์ของฉัน (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
คุณแน่ใจหรือไม่ว่า“ ”1” เหล่านั้นจะไม่ถูกตีความว่าเป็น 0xFFFF … FF ทำให้โปรแกรมของคุณร้องขอหน่วยความจำแอดเดรสทั้งหมด
Anton Samsonov

0

หลังจากลองใช้มาโครที่กล่าวถึงแล้วส่วนนี้ดูเหมือนจะให้ผลลัพธ์ที่ต้องการ ( 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);

วิ่ง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];

วิ่ง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 ไม่ใช่คำตอบของทุกสิ่ง ...


0

เพื่อตรวจสอบขนาดของโครงสร้างข้อมูลกับข้อ จำกัด ในเวลาคอมไพล์ฉันใช้เคล็ดลับนี้

#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

หากขนาดของ x มากกว่าหรือเท่ากับขีด จำกัด MAX_SIZEOF_X ดังนั้น gcc จะบ่นว่ามีข้อผิดพลาด "ขนาดของอาร์เรย์ใหญ่เกินไป" VC ++ จะออกข้อผิดพลาด C2148 ('ขนาดรวมของอาร์เรย์ต้องไม่เกิน 0x7fffffff ไบต์') หรือ C4266 'ไม่สามารถจัดสรรอาร์เรย์ที่มีขนาดคงที่ 0'

จำเป็นต้องใช้คำจำกัดความสองคำเนื่องจาก gcc จะอนุญาตให้กำหนดอาร์เรย์ขนาดศูนย์ด้วยวิธีนี้ (ขนาด x - n)


-10

ตัวsizeofดำเนินการไม่พร้อมใช้งานสำหรับตัวประมวลผลก่อน แต่คุณสามารถถ่ายโอนsizeofไปยังคอมไพเลอร์และตรวจสอบเงื่อนไขในรันไทม์:

#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
จะปรับปรุงคำตอบที่ยอมรับไปแล้วอย่างไร วัตถุประสงค์ใดที่กำหนดcompiler_sizeให้บริการ? ตัวอย่างของคุณพยายามจะแสดงอะไร?
ugoren
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.