代码之家  ›  专栏  ›  技术社区  ›  Unmanned Player

如何正确声明常量字符串数组?

c
  •  1
  • Unmanned Player  · 技术社区  · 6 年前

    这是一个愚蠢的问题,但我似乎弄不好。我又碰到一个 question ,但是给出的答案不能正确地解决我的特定用例中的警告。

    我试图声明一个常量字符串数组作为 argv 在里面 posix_spawn 但GCC抱怨 const 被丢弃。请参见下面的示例代码:

    #include <stdio.h>
    
    /* Similar signature as posix_spawn() shown for brevity. */
    static void show(char *const argv[])
    {
        unsigned i = 0;
    
        while(argv[i] != NULL) {
            printf("%s\n", argv[i++]);
        }
    }
    
    int main(void)
    {
        const char exe[] = "/usr/bin/some/exe";
    
        char *const argv[] = {
            exe,
            "-a",
            "-b",
            NULL
        };
    
        show(argv);
    
        return 0;
    }
    

    并将其编译为:

    gcc -std=c89 -Wall -Wextra -Wpedantic -Wwrite-strings test.c -o test 
    test.c: In function ‘main’:
    test.c:17:9: warning: initializer element is not computable at load time [-Wpedantic]
             exe,
             ^
    test.c:17:9: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
    test.c:18:9: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
             "-a",
             ^
    test.c:19:9: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
             "-b",
             ^
    

    既然, exe 它本身就是一个常量字符串 "-a" "-b" 我觉得是对的。但海湾合作委员会似乎不同意。正在删除 exe 从阵列中删除 -Wwrite-strings 编译时没有警告。也许我错过了一些太基本的东西。

    如何声明字符串的常量数组?

    2 回复  |  直到 6 年前
        1
  •  0
  •   M.M    6 年前

    所讨论的功能, posix_spawn ,需要指向(第一个元素)非常量指针数组的指针 char . 尽管如此,函数不会修改字符串。

    出于历史原因,这种C API中常量正确性差的情况并不少见。

    在标准C中,可以将字符串文本传递给它而不发出任何警告,因为字符串文本的类型为:非常量字符数组。(修改它们是未定义的行为,不需要诊断)。

    如果你使用 -Wwrite-strings 标记尝试获取有关潜在UB的警告,然后它将为您提供这些误报,用于与非常量正确的API交互的情况。


    使用C89中的API的预期方法是:

    char exe[] = "/usr/bin/some/exe";
    
    char * argv[] = {
        NULL,
        "-a",
        "-b",
        NULL
    };
    
    argv[0] = exe;
    show(argv);
    

    注意,在C89中,不可能让您的阵列 const 也可以用局部变量初始化 exe . 这是因为C89要求有支撑列表中的所有初始值设定项 常量表达式 ,其定义不包括局部变量的地址。

    如果你想用 -wwrite字符串 然后可以使用 (char *)"-a" 而不是 "-a" 等等。


    在评论中建议使用 const char * 对于字符串类型,然后使用强制转换,如下所示:

    const char exe[] = "/usr/bin/some/exe";
    
    const char * argv[] = {
        NULL,
        "-a",
        "-b",
        NULL
    };
    argv[0] = exe;
    show((char **)argv);
    

    但这可能违反了 严格的别名规则 . 即使规则允许 const T 化名为 T ,此规则不“递归”;可能不允许使用别名 常量字符* 作为 char * 尽管规则的措辞不是100%清楚。 See this question for further discussion .

    如果你想提供一个接受 const char ** 和电话 posix_繁殖 ,并且不违反c标准,那么您实际上必须将指针列表的副本复制到 烧焦* . (但您不必实际复制字符串内容)

        2
  •  1
  •   Daniel H    6 年前

    声明 char *const argv[] 使 argv 指向的常量指针数组 可变的 char 一旦你创建了数组,你就不能改变指针指向的地方(你不能说 argv[0] = "/bin/some_other_program" 但是你可以改变角色本身(所以 argv[1][1] = 'b' ,让你通过 -b 相反)。

    但是,您指定的指针是 小精灵 是指向常量的指针 烧焦 S.即使是 阿尔加夫 会让你,因此警告。由于无法更改函数的类型,因此需要去掉 const 不知何故。使用的选项 strdup ,将字符串放入 char[] 变量(隐式地也复制它们),或者丢弃 康斯特 在这个级别上(如果程序实际修改了值,则为ub,如果程序没有修改值,则应该是安全的)。 Here 是使用后两种方法的示例。