如何正确声明一个const字符串数组?

时间:2018-05-29 22:05:43

标签: c

这是一个愚蠢的问题,但我似乎无法做到这一点。我遇到了另一个question,但给出的答案没有正确解决我特定用例中的警告。

我正在尝试声明一个常量字符串数组在posix_spawn函数中作为argv传递,但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编译而不发出警告。也许我错过了一些太基础的东西。

如何声明const数组的字符串?

2 个答案:

答案 0 :(得分:1)

声明char *const argv[]使argv成为 mutable char的常量指针数组。创建数组后,您无法更改指针指向的位置(您不能说argv[0] = "/bin/some_other_program"),但您可以自行更改字符(argv[1][1] = 'b',以便通过{-b {1}}代替)。

但是,您指定为argv元素的指针是指向常量char的指针。即使argv的类型允许你,实际执行任何这些分配也是未定义的行为,因此警告。由于您无法更改函数的类型,因此您需要以某种方式摆脱const的问题。选项是使用strdup,将字符串放入char[]变量(隐式地复制它们),或者抛弃const这个级别(如果程序实际上是UB)修改值但如果不安全则应该是安全的。 Here是使用后两种方法的示例。

答案 1 :(得分:0)

有问题的函数posix_spawn需要一个指向非const char指针数组(第一个元素)的指针。尽管如此,该函数不会修改字符串。

由于历史原因,C API中的const-correctness不良的情况并不少见。

您可以在标准C中没有任何警告的情况下将字符串文字传递给它,因为字符串文字的类型为:非const char数组。 (修改它们是未定义的行为,无需诊断)。

如果您使用-Wwrite-strings标志尝试获取有关潜在UB的警告,那么对于与非const-correct 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-strings,则可以使用(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,此规则也不会“递归”;可能不允许将const char *别名为char *,尽管该规则的措辞并非100%明确。 See this question for further discussion

如果你想提供一个接受const char **并调用posix_spawn并且不违反C标准的包装器,那么你实际上必须将指针列表的副本复制成{ {1}}。 (但您不必实际复制字符串内容)