【问题标题】:Generate compiler warning if const char* array initialization comma is missing如果缺少 const char* 数组初始化逗号,则生成编译器警告
【发布时间】:2020-05-12 13:59:15
【问题描述】:

我在我的 C 代码中经常使用字符串文字表。这些表或多或少都像这样:

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

上面代码的问题是,如果表格变长了,在开发过程中被修改了,我时不时会忘记逗号。代码编译时没有逗号,但我的程序最终崩溃,因为最后一个字符串设置为NULL。我使用了 MinGW 和 Keil 编译器来验证。

如果缺少逗号,是否有任何方法可以为我的初始化生成编译器警告?

【问题讨论】:

  • 当您忘记向该表添加状态时会发生什么?
  • @Jeroen3 true 这会导致同样的错误。使用静态断言针对 STATE_AMOUNT 测试列表长度也可以解决此问题。

标签: c initialization


【解决方案1】:

将每个const char* 括在一对括号中应该可以解决问题,如下面的sn-p 所示:

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

LIVE DEMO


请注意,如果您忘记了逗号,实际发生的情况是 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 generates(其他编译器生成类似代码):

.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

很明显,最后三个字符串是连接在一起的,并且数组的长度不是您期望的。

【讨论】:

    【解决方案2】:

    如果出现意外结果,您可以让编译器对数组进行计数并生成错误消息:

    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" );
    

    See this thread 如果您的编译器太旧且不支持它,请参考实现 _Static_assert 的想法。

    另外,当您添加新状态但忘记更新字符串表时,这也有帮助。但你可能也想研究一下 X 宏。

    【讨论】:

    • 该死的......这正是我要输入的确切答案!
    【解决方案3】:

    这不会让编译器帮助您,但我发现像下面这样编写它可以让人类更容易不删除逗号:

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

    【讨论】:

    • 在最后添加一些东西也更容易。您不必编辑前一行来添加逗号。 (缺少逗号的主要原因。)
    • @datafiddler:同意。当您对它们进行注释和取消注释时,它对于微调 SQL SELECT 命令中的列列表也很有用。你经常想改变最后一个;你很少想改变第一个。这样您就不必修改多行来注释掉一个项目。
    【解决方案4】:

    我一直使用对显式大小数组的引用来解决这个问题。

    // 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;
    

    【讨论】:

    • 静态断言似乎是一个更优雅的解决方案。我想你有在静态断言作为语言的一部分实现之前这样做的习惯吗?您现在是否仍然看到与验证数组预期大小的静态断言相比有什么优势?
    • @CodyGray:是的,既然你提到它,这是静态断言
    猜你喜欢
    • 2016-05-29
    • 2017-08-11
    • 1970-01-01
    • 1970-01-01
    • 2010-12-05
    • 2020-08-10
    • 1970-01-01
    • 2011-04-18
    • 1970-01-01
    相关资源
    最近更新 更多