字符串文字是常量吗?

时间:2010-12-20 19:31:26

标签: c string constants const literals

如果我将字符串文字分配给char*,即使使用了许多迂腐选项(-Wall -W -pedantic -std=c99),GCC和Clang都不会抱怨:

char *foo = "bar";

当他们(当然)在我const char*分配char*时会抱怨。

这是否意味着字符串文字被认为是char*类型?他们不应该const char*吗?如果它们被修改,它就不是定义的行为!

(一个不相关的问题)命令行参数(即:argv):它被认为是一个字符串文字数组?

8 个答案:

答案 0 :(得分:20)

它们属于char[N]类型,其中N是包含终止\0的字符数。所以是的,您可以将它们分配给char*,但您仍然无法写入它们(效果将是未定义的)。

Wrt argv:它指向一个指向字符串的指针数组。这些字符串是明确可修改的。您可以更改它们,并且需要保留最后存储的值。

答案 1 :(得分:6)

为了完整起见,6.4.5 字符串文字第5段中的C99 draft standard C89和C11具有相似的措辞)表示:

  

值为零的字节或代码附加到由字符串文字或文字产生的每个多字节字符序列。然后使用多字节字符序列初始化静态存储持续时间和长度的数组,足以包含序列。对于字符串文字,数组元素具有类型char ,并使用多字节字符序列的各个字节进行初始化; [...]

所以这表示字符串文字具有静态存储持续时间(持续程序的生命周期),其类型为char[](不是{{1}并且它的长度是字符串文字的大小,附加零。 *第6段说:

  

如果程序试图修改这样的数组,则行为是未定义的。

因此,尝试修改字符串文字undefined behavior,无论它们不是char *

关于const 计划启动第2段中的argv说:

  

如果声明它们,主函数的参数应遵循以下内容   约束:

     

[...]

     

- 参数argc和argv以及argv数组指向的字符串应该可由程序修改,并在程序之间保留它们最后存储的值   启动和程序终止。

因此5.1.2.2.1不被视为字符串文字数组,可以修改argv的内容。

答案 2 :(得分:5)

使用-Wwrite-strings选项,您将获得:

warning: initialization discards qualifiers from pointer target type

无论选项如何,GCC都会将文字放入只读内存部分,除非使用-fwritable-strings另有说明(但此选项已从最近的GCC版本中删除)。

命令行参数不是常量,它们通常存在于堆栈中。

答案 3 :(得分:3)

(对不起,我刚才注意到这个问题被标记为c,而不是c++。也许我的回答毕竟与这个问题无关!)

字符串文字不完全constnot-const,文字有一个特殊的奇怪规则。

摘要:文字可以作为foo( const char (&)[N])引用到数组,不能作为非const数组。他们更喜欢来衰减到const char *。到目前为止,这看起来好像是const但是有一个特殊的遗留规则允许文字衰减到char *。请参阅下面的实验。)

(在使用-std=gnu++0x的clang3.3上进行了实验。也许这是一个C ++ 11问题?或者特定于clang?无论哪种方式,都会发生一些奇怪的事情。)

首先,文字似乎是const

void foo( const char  * ) { std::cout << "const char *" << std::endl; }
void foo(       char  * ) { std::cout << "      char *" << std::endl; }

int main() {
        const char arr_cc[3] = "hi";
        char arr_c[3] = "hi";

        foo(arr_cc); // const char *
        foo(arr_c);  //       char *
        foo("hi");   // const char *
}

两个数组的行为与预期一致,表明foo能够告诉我们指针是否为const。然后"hi"选择constfoo。所以似乎解决了这个问题:文字是const ......不是吗?

但是,如果您删除void foo( const char * ),那就会变得奇怪。首先,对foo(arr_c)的调用在编译时失败并出现错误。这是预料之中的。但是文字调用(foo("hi"))通过非const调用工作。

因此,文字比arr_c“更常见”(因为它们更倾向于衰减到const char *,与arr_c不同。但文字比{{1}更“常量”因为如果需要,他们愿意衰为arr_cc

(Clang在衰变到char *时会发出警告。)

但腐烂怎么办?我们为了简单起见而避免使用它。

让我们通过引用将数组转换为foo。这为我们提供了更直观的结果:

char *

和以前一样,文字和const数组(void foo( const char (&)[3] ) { std::cout << "const char (&)[3]" << std::endl; } void foo( char (&)[3] ) { std::cout << " char (&)[3]" << std::endl; } )使用const版本,arr_cc使用非const版本。如果我们删除arr_c,那么foo( const char (&)[3] )foo(arr_cc);都会出错。简而言之,如果我们避免使用指针衰减并使用引用数组,则文字的行为就像它们是foo("hi");一样。

<强>模板吗

在模板中,系统会推断出const而不是const char *,而你却“卡住”了。

char *

基本上,文字在任何时候都表现为template<typename T> void bar(T *t) { // will deduce const char when a literal is supplied foo(t); } ,除非您直接使用文字初始化const

答案 4 :(得分:2)

Johannes关于类型和内容的回答是正确的。但除此之外,是的,修改字符串文字的内容是未定义的行为。

关于argv的问题:

  

参数argc和argv以及   argv数组指向的字符串   应由程序修改,   并保留其最后存储的值   程序启动和程序之间   终止。

答案 5 :(得分:1)

在C89和C99中,字符串文字的类型为char *(由于历史原因,据我所知)。你是正确的,试图修改一个导致未定义的行为。 GCC有一个特定的警告标志-Wwrite-strings(不属于-Wall),如果您尝试这样做会发出警告。

至于argv,参数会被复制到程序的地址空间中,并且可以在main()函数中安全地修改。

编辑:哎呀,意外复制了-Wno-write-strings。更新了警告标志的正确(正面)形式。

答案 6 :(得分:1)

它们是const char *,但是有一个特定的排除,它将它们分配给char *,用于const之前存在的遗留代码。命令行参数绝对不是文字的,它们是在运行时创建的。

答案 7 :(得分:1)

字符串文字具有正式类型char [],但语义类型const char []。纯粹主义者讨厌它,但这通常是有用的和无害的,除了带来很多新手的SO“为什么我的程序崩溃?!?!”的问题。