递归在main参数中调用自身(结合VLA与sizeof)是否符合C99标准?
本问题无实际应用价值,仅探讨两段疑似违反规则且可编译的代码(不同编译器的警告/错误情况不同)。
第一段符合C99标准的代码
#include <stdio.h> #include <stddef.h> int main(void) { size_t n = sizeof(int[printf("%s", "Hello")]); }
这段代码合法的依据是:C99标准中,sizeof运算符仅在操作数为变长数组(VLA)时才会求值其大小表达式——这里int[printf(...)]是VLA,因此printf会被执行,符合标准规则。
第二段存疑的代码
#include <stdio.h> int main(int argc, char** argv); int main(int argc, char* argv[sizeof(int[printf("%s\n", "Hello") + main(0, NULL)])]) {}
1. 使用sizeof(某VLA)作为数组大小是否合法?
合法。
根据C99 §6.7.5.2 数组声明符:
若大小表达式为整数常量表达式且元素类型拥有已知常量大小,则数组类型为普通数组类型;否则为变长数组类型。
同时C99 §6.5.3.4 sizeof运算符规定:
当sizeof作用于变长数组类型的对象时,其大小在运行时计算。
标准未禁止将sizeof(VLA)的结果作为另一个数组的大小,哪怕这个结果是运行时确定的——此时该数组会被视为变长数组,符合规则。
2. main函数声明为int main(int, char**)但定义为int main(int, char*[某大小])是否合法?
合法。
根据C99 §6.7.5.3 函数声明符:
声明为“类型数组”的函数参数会被调整为“指向该类型的指针”。
也就是说,函数参数中的char* argv[N](无论N是编译期常量还是运行时值)都会被自动调整为char** argv,和声明中的char** argv类型完全兼容,因此main的声明与定义参数匹配,符合标准。
3. 在函数自身的参数中递归调用该函数是否合法?
不合法,属于未定义行为。
根据C99 §6.9.1 函数定义:
函数参数声明列表中,每个参数的作用域从其声明点开始,到函数定义结束。在参数声明中引用函数自身标识符时,该函数的声明尚未完成——此时函数的类型未完全确定,这种递归引用属于非法的向前引用。
另外,C99 §6.2.1 作用域规则也明确:函数标识符在其声明/定义中可见,但参数声明阶段函数尚未完成声明,此时的自引用无效。即使是普通函数,这种写法也违反标准,更不用说main函数本身有特定的启动规范,这种用法完全不符合要求。
4. 是否能保证递归会执行,编译器是否有权将其优化掉?若递归执行,是否必然会发生栈溢出?
由于问题3中已确定该写法属于未定义行为,C标准不保证任何特定行为:编译器可以选择执行递归、优化掉调用、甚至直接终止程序,结果完全不可预测。
如果假设编译器执行了递归调用,那么会触发无限递归,每次调用main都会向栈中压入栈帧,最终必然导致栈溢出,但这只是一种可能的结果,并非标准规定的必然行为——未定义行为下任何结果都允许。
内容的提问来源于stack exchange,提问作者Chi_Iroh

