สร้างคำเตือนคอมไพเลอร์หากเครื่องหมายจุลภาคการเริ่มต้น const char * array หายไป


53

ฉันใช้ตารางตัวอักษรเป็นจำนวนมากในรหัส C ของฉัน ตารางเหล่านี้ทั้งหมดดูเหมือนมากขึ้นหรือน้อยลง:

static const char* const stateNames[STATE_AMOUNT] =
{
    "Init state",
    "Run state",
    "Pause state",
    "Error state",
};

ปัญหาของโค้ดข้างต้นคือถ้าตารางยาวขึ้นและถูกแก้ไขระหว่างการพัฒนาฉันลืมเครื่องหมายจุลภาคเป็นครั้งคราว คอมไพล์รหัสโดยไม่ต้องมีปัญหากับเครื่องหมายจุลภาคหายไป แต่ปลายโปรแกรมของฉัน up crashing NULLเป็นสตริงที่ผ่านมามีการตั้งค่า ฉันใช้คอมไพเลอร์ MinGW และ Keil เพื่อตรวจสอบ

มีวิธีใดในการสร้างคำเตือนคอมไพเลอร์สำหรับการเริ่มต้นของฉันหากคอมม่าหายไป?


1
จะเกิดอะไรขึ้นเมื่อคุณลืมเพิ่มสถานะลงในตารางนี้
Jeroen3

1
@ Jeroen3 จริงสิ่งนี้จะทำให้เกิดข้อผิดพลาดเดียวกัน การใช้การยืนยันแบบคงที่การทดสอบความยาวของรายการเทียบกับ STATE_AMOUNT จะแก้ปัญหานี้ได้เช่นกัน
จอนนี่ชูเบิร์ต

คำตอบ:


62

การห่อทุกอย่างconst char*ไว้ในวงเล็บควรแก้ปัญหาดังที่แสดงในตัวอย่างต่อไปนี้:

static const char* const stateNames[5] =
{
    ("Init state"),
    ("Run state"),
    ("Pause state")     //comma missing
    ("Pause state3"),
    ("Error state")
};

หากคุณลืมเครื่องหมายจุลภาคคุณจะได้รับข้อผิดพลาดในการรวบรวมคล้ายกับ: error: called object is not a function or function pointer

สาธิตสด


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

static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state" //comma missing
    "Pause state3" //comma missing
    "Error state"
};

int main(void)
{  
    printf("%s\n", stateNames[0]);
    return 0;    
}

นี่คือสิ่งที่gcc-9.2สร้าง (คอมไพเลอร์อื่นสร้างรหัสที่คล้ายกัน):

.LC0:
        .string "Init state"
        .string "Run state"
        .string "Pause statePause state3Error state" ; oooops look what happened
        .quad   .LC0
        .quad   .LC1
        .quad   .LC2
main:
        push    rbp
        mov     rbp, rsp
        mov     eax, OFFSET FLAT:.LC0
        mov     rdi, rax
        call    puts
        mov     eax, 0
        pop     rbp
        ret

เป็นที่ชัดเจนว่าสตริงสามตัวสุดท้ายเรียงต่อกันและอาร์เรย์ไม่ยาวเท่าที่คุณคาดหวัง


33

คุณสามารถให้คอมไพเลอร์นับอาร์เรย์และสร้างข้อความแสดงข้อผิดพลาดหากผลลัพธ์ที่ไม่คาดคิด:

enum { STATE_AMOUNT = 4 };

static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state"    // <--- missing comma
    "Error state",
};

_Static_assert( sizeof stateNames / sizeof *stateNames == STATE_AMOUNT,
        "oops, missed a comma" );

ดูหัวข้อนี้เพื่อหาแนวคิดในการนำไปใช้_Static_assertหากคอมไพเลอร์ของคุณเก่ามากและไม่รองรับ

เป็นโบนัสสิ่งนี้สามารถช่วยได้เมื่อคุณเพิ่มสถานะใหม่ แต่ลืมปรับปรุงตารางสตริง แต่คุณอาจต้องการค้นหา X Macros ด้วย


ประณาม .... นี่คือคำตอบที่แน่นอนที่ฉันจะพิมพ์!
ช่างเชื่อม

11

ฉันใช้การอ้างอิงไปยังอาร์เรย์ที่มีขนาดชัดเจนเสมอเพื่อแก้ปัญหานี้

// no explicit size here
static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state",
    "Error state",
};
static const char* const (&stateNameVerifier)[STATE_AMOUNT] = stateNames;

http://coliru.stacked-crooked.com/a/593fc2eac80782a6

main.cpp:10:32: error: reference to type 'const char *const [5]' could not bind to an lvalue of type 'const char *const [4]'
static const char* const (&stateNameVerifier)[STATE_AMOUNT] = stateNames;

4
การยืนยันแบบคงที่ดูเหมือนจะเป็นทางออกที่สง่างามกว่ามาก ฉันคิดว่าคุณมีนิสัยในการทำสิ่งนี้ก่อนที่จะใช้การยืนยันแบบคงที่ซึ่งเป็นส่วนหนึ่งของภาษา ตอนนี้คุณยังคงเห็นข้อได้เปรียบของสิ่งนี้มากกว่าการยืนยันแบบคงที่ซึ่งยืนยันขนาดที่คาดหวังของอาร์เรย์หรือไม่?
Cody Gray

2
@CodyGray: ใช่นี้เป็นแบบคงที่ยืนยันก่อนที่คุณพูดถึงมัน
Mooing Duck

9

สิ่งนี้ไม่ได้นำคอมไพเลอร์มาช่วยคุณ แต่ฉันพบว่าการเขียนแบบด้านล่างช่วยให้มนุษย์ไม่ต้องใช้เครื่องหมายจุลภาค:

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};

3
การต่อท้ายสิ่งต่างๆก็ง่ายขึ้นเช่นกัน คุณไม่ต้องแก้ไขบรรทัดก่อนหน้าเพื่อเพิ่มเครื่องหมายจุลภาค (สาเหตุหลักของเครื่องหมายจุลภาคที่หายไป)
datafiddler

@datafiddler: เห็นด้วย นอกจากนี้ยังมีประโยชน์ในการปรับแต่งรายการคอลัมน์ในคำสั่ง SQL SELECT เมื่อคุณทำการรับและยกเลิกการใส่เครื่องหมายความคิดเห็น คุณมักต้องการเปลี่ยนอันสุดท้าย คุณไม่ค่อยต้องการเปลี่ยนคนแรก วิธีนี้คุณไม่จำเป็นต้องแก้ไขหลายบรรทัดเพื่อคอมเม้นต์หนึ่งรายการ
JonathanZ สนับสนุน MonicaC
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.