【问题标题】:How to correctly declare a const string array?如何正确声明一个 const 字符串数组?
【发布时间】:2018-05-29 22:05:42
【问题描述】:

这是一个愚蠢的问题,但我似乎无法正确回答。我遇到了另一个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",我认为它是正确的。但 GCC 似乎不同意。从数组中删除 exe 并删除 -Wwrite-strings 编译时不会出现警告。也许我错过了一些太基本的东西。

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

【问题讨论】:

  • “我遇到了另一个问题” - 该问题的公认答案是正确的(并且没有“警告”),但是该问题不涉及const,因此不是明确你为什么链接它
  • 该操作试图将文字常量字符串放入非常量数组中。 GCC 通常会发出警告。在我的情况下,由于-Werror,它会失败并出现错误。
  • 字符串文字在 C 中不是 const。代码是正确的。您看到警告是因为您使用了开关 -Wwrite-strings,它有意为正确的代码生成警告。
  • 我认为字符串文字存储在只读部分中,而与警告开关无关,除非还使用了-fwritable-strings。但是你是对的,标准并没有指定这种行为。更正了我的问题的措辞。谢谢。
  • 它们可以存储在只读区域,但无论哪种方式都没有const 限定符。在标准 C 中,这只是未定义的行为,无需诊断即可写入它们。-Wwrite-strings 尝试捕获写入尝试,但当您需要调用不正确的 API 时也会给出误报

标签: c


【解决方案1】:

声明char *const argv[] 使argv 成为一个指向可变 chars 的常量指针数组。一旦你创建了数组,你就不能改变指针指向的位置(你不能说argv[0] = "/bin/some_other_program"),但是你可以改变字符本身(所以argv[1][1] = 'b',这样你就可以通过-b)。

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

【讨论】:

  • 像您的建议一样更改它与 posix_spawn() 函数签名不匹配。见pubs.opengroup.org/onlinepubs/009696899/functions/…。这是否意味着,我必须 strdup() argv 中的每个字符串?
  • 不,如果您创建指向非const chars 的指针数组(例如,从exe 中删除const),它应该可以工作。但我有些惊讶;我不希望posix_spawn 想要实际更改传入的值,所以我不知道为什么它希望它们可变。我不确定,但我也认为只要它实际上不改变任何东西,它就完全定义为抛弃指针的constness。
  • 一旦指针变深, const 就变得非常棘手和难以理解:stackoverflow.com/questions/5055655/…
  • @EvanBenn 是的,当然。我想这可能会改变为什么 API 设计为非constchars 的细节,但我认为这不会改变我的答案。
  • @EvanBenn 有道理。但是我该如何避免这种特殊情况呢?使用pragama?
【解决方案2】:

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

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

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

如果您使用 -Wwrite-strings 标志来尝试获取有关潜在 UB 的警告,那么在您与非 const 正确 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"等来抑制警告。


在 cmets 中建议使用 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 标准,那么您实际上必须将指针列表复制到char * 的数组中。 (但您不必实际复制字符串内容)

【讨论】:

  • 我不能修改posix_spawn的签名,所以它接受const char *const [],不是吗?此外,您的答案适用于这种情况。我已将代码更改为包装 posix_spawn,因此它的工作方式与您在答案中显示的一样。
猜你喜欢
  • 2011-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 1970-01-01
  • 2016-07-06
  • 1970-01-01
相关资源
最近更新 更多