为什么 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 strcpy
and 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
选项。
在 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 strcpy
and 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
选项。