为什么 strcpy 编译为文字?

Why does strcpy to a literal compile?

在 Visual Studio 2015 年,以下编译:

strcpy("destination", "Source");

编译器难道不应该弄清楚 "destination" 是一个文字,不能构成一个有效的非常量 char* 参数吗?

附带说明一下,它确实 "correctly" 在 运行 时崩溃。

上下文

字符串字面量在 C 语言中一直是非常量。当前标准草案 n1570 在 6.4.5/6 中说:

The multibyte character sequence [resulting from concatenation of adjacent string literals, -ps] is then used to initialize an array of static storage duration and length just sufficient to contain the sequence. For character string literals, the array elements have type char [and not const char, -ps].

原因当然是,最初它们确实通常是可写的。程序本身是可写的;甚至还有自修改代码。这是相关的,因为字符串文字是由编译器生成和存储的 "together with the program"。

这是现代内存管理——即高级机器体系结构的问题——这使得在访问程序内存时完全有可能生成硬件异常。使用这种可能性是一个安全问题。并非所有体系结构(都可以)做到这一点,即使在今天,编译器也可以选择控制字符串的去向(例如 -fwritable-strings with old gccs)。

此代码

代码在语法上是兼容的,在语义上它是 n1570 中 6.4.5/7 的 UB:“如果程序试图修改这样的数组,行为是 未定义。”

当字符串文字的地址被分配给非常量变量(或用于初始化函数调用中的非常量参数)时,编译器可能会发出警告,但我尝试过的常见地址不会发出警告,这让我有点困惑 - - 许多已实施的警告似乎不那么重要且嘈杂。

strcpy()

关于strcpy()的具体情况:有评论说"the compiler doesn't know what strcpy() does"。这往往具有误导性:

  • 标准库函数由标准明确定义。这些知识可以在编译器中使用。例如,像 lint 这样的工具通常知道这样的语义。
  • 编译器和默认标准库通常是紧密合作开发的,并且是捆绑在一起的;因为在编译库本身、编译器引导等方面,编译器和库之间存在大量交互。两个项目之间通常会定期进行交流。
  • 编译器可以自由地将库函数替换为内部函数,这会给他们非常深入的知识。

gcc

的确,gcc happens to replace strcpyand many other functions with built-ins,所以它确实有第一手资料,第一个地址将被写入。它只是不使用它。

另一个 gcc 内在函数是 printf(),这里编译器使用其对 printf 语义的了解来警告格式错误。这清楚地表明 strcpy() 也可能发出警告。

顺便说一句,gcc 确实 警告 "abc"[1] = 0;。这很有趣,因为我认为 strcpy() 内在函数会被内联(它必须很短),因此 -O3 并且可能 -flto 在某些时候相当于 "destination"[i] = "Source"[i];实际上对编译器可见并触发相同的警告。

其他编译器

我测试了 VC 2013、gcc 5.3.0、gcc 4.7.2 和 clang 3.7.1。其中 None 发出了将字符串文字传递给 strcpy() 的警告,但 cremno 指出 VC 提供了捕获错误的 /analyze 选项。