为什么 GCC 和 Clang 使用此一致的 C 代码产生不同的输出:
int (puts) (); int (main) (main, puts) int main;
char *puts[(&puts) (&main["\0April 1"])]; <%%>
Run Code Online (Sandbox Code Playgroud)
即使使用 ,编译器也不会产生任何警告或错误,但使用 GCC 构建时,-Wall -std=c18 -pedantic程序不会产生任何输出,但使用 Clang 构建时,会打印当前日期。
Joh*_*ger 11
为什么 GCC 和 Clang 使用此一致的 C 代码产生不同的输出:
Run Code Online (Sandbox Code Playgroud)int (puts) (); int (main) (main, puts) int main; char *puts[(&puts) (&main["\0April 1"])]; <%%>
首先,它是一致的代码,尽管它确实使用了可变长度数组,这是 C11 和 C17 中的可选语言功能。一些混淆是
<%和,其含义与和%>相同{}相同。puts非原型函数的前向声明main
main()puts和)mainmain程序入口点函数之外的其他用途[]索引运算符 ( )
的操作数的常规顺序反转一个不太混乱的等价物是
int puts();
int main(
int argc,
char *argv[ puts("\0April 1" + argc) ]
) {
}
Run Code Online (Sandbox Code Playgroud)
但是,关于使用 GCC 编译的版本和使用 Clang 构建的版本之间行为差异的核心问题在于 VLA 函数参数大小的表达式是否在运行时计算。
语言规范规定,当使用数组类型声明函数参数时,其类型将“调整”为相应的指针类型。这同样适用于完整、不完整和可变长度数组类型,但规范没有明确说明不计算维度的表达式。它确实指定在某些其他情况下不评估表达式,甚至在涉及 VLA 的表达式的情况下对此类规则进行例外处理sizeof,因此这种情况下的省略可以解释为有意义。
这仅对 VLA 类型的参数产生影响,因为只有对那些维度表达式的求值才能对机器状态产生副作用,包括但不限于可观察的程序行为。
GCC 不会在运行时计算 VLA 参数的大小表达式,我倾向于认为这符合标准的意图。结果,GCC 编译的程序除了以状态 0 退出之外什么也不做。
Clang 在运行时评估 VLA 参数的大小表达式。尽管我不赞成对规范的这种解释,但我不能排除它。当它计算大小表达式时,它使用传递的第一个参数的值。当程序不带参数运行时,第一个参数的值为 1,结果是puts使用指向'A'in的指针调用标准库的函数"\0April 1"。