C语言中字符串是如何工作的?

3

C编程语言中,字符串被认为是一个常量。

因此,当我声明char *s = "Hello"时,我知道s指向内存中的H,因为"Hello"被存储在程序的静态内存中,并且"Hello"是不可改变的。

这是否意味着变量s现在是指向常量数据的指针的变量,例如const int a=3; const int *i=&a;。似乎是这样,因为我不能操作数据(如果这样做,会导致分段错误)。

但是,如果是这样,编译器不应该能够检测并说我已经把有资格的数据分配给了无资格的变量吗?像char *p p是指向无资格字符的指针,当我说char *p="Hello"时,p,指向无资格字符的指针不能指向常量字符类型?

我在这里错过了什么?

如果情况不是上述那样,那么如何使常量字符数组成为不可变的?


1
您有没有查看过C11标准文档n1570? 您真的应该下载并阅读该规范。 另外, ansi-c (甚至是 C99)都已经过时了。 - Basile Starynkevitch
变量s不是常量(除非另有说明),您仍然可以使用它来指向其他位置。 "Hello" 存储在程序的 data section 部分,这就是为什么它是不可变的。 - qwn
2个回答

4
首先,在C语言中,字符串并非不可改变。C语言甚至没有字符串类型 -- 字符串只是以以'\0'结尾的char序列定义而已。
你所讨论的是字符串字面值,它们可以是不可改变的。C标准定义了尝试修改字符串字面值将会导致未定义的行为,但它们的类型是char *。因此,如果你确信在你的C实现中,字符串字面值是可写的,那么你就可以这样做!*)
但你的代码将不再是良好定义的C代码,并且不能在其他具有只读字符串字面值的平台上工作。它将能够编译通过,因为通过char *进行写入是完全可以的,但在运行时会以不可预测的方式失败(例如,可能会崩溃)。
因此,对于可移植的代码,最佳实践是仅将字符串字面值分配给const char *指针,并且如果需要可变字符串,则将字符串字面值用作char []的初始化器。
*) 注意,这种情况非常不常见,现代平台都会将字符串字面值放置在只读数据段或类似的位置。

即使您确定字符串字面值在您的实现中是可变的,但突变仍然是未定义行为,因此您不能这样做。同样,您可能确信您的实现不会在算术溢出时陷入困境--事实上,GCC被记录为不会陷入困境--但编译器仍然可能产生令人惊讶的结果。请参见https://blog.regehr.org/archives/759 - rici
无论你是否认为它是一个“UB技巧”,事实是编译器可能会将布尔值x < x + 1常量折叠为1,即使对于特定的x值它会评估为0,因为它只在UB的情况下评估为0。同样,如果编译器知道char* p的值是指向字符串字面量的指针,它可以选择不编译*p = 'a';,甚至可以选择不编译该基本块中的以下代码,因为程序员必须做一些保证以确保UB不会发生。 - rici
你有提到的现实参考吗?也就是说,有一种编译器可以使字符串字面量可变吗?如果没有,那么这都是理论上的,不是吗?如果有,你怎么知道编译器永远不会获得我所说的优化呢?当GCC获得这些优化时,它确实让Linux作者感到惊讶,还有其他人,他们有充分的理由相信自己的平台不会陷入整数溢出的陷阱。无论如何,这就是我的全部内容。 - rici
例如,使用命令行选项 --writable-stringscc65 可以为您提供这种保证。我仍然编辑了答案,使关于依赖此类事物的 警告 更加明显。但实际上,UB 并不意味着“你不能那样做”,它只是意味着“您的代码不是定义良好的 C 代码,因此可能很容易出错”。 - user2371524

3

语法 char *s = "Hello"; 是在C规范中未包含const关键字时引入的。后来为了向后兼容而保留。尝试写入这样的 s[i] 将导致未定义行为。(在您的情况下观察到几次段错误)

这种行为(将字符串文字或const char []转换为非常量char *)在C++11之前曾短暂支持,然后被弃用。

C中的类型安全性有限。


1
“简单来说,直到C++11?”那大概是22年左右了。你认为这算是很长的时间吗? :) - rici

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接