Why does declaring main as an array compile?
我在CodeGolf上看到了一段代码,它的目的是作为编译器炸弹,其中
1 | int main[1] = { 0 }; |
它似乎在Clang下编译得很好,并且在GCC下只有一个警告:
warning: 'main' is usually a function [-Wmain]
结果二进制文件当然是垃圾。
但为什么它会编译呢? 是否允许C规范? 我认为相关的部分说:
5.1.2.2.1 Program startup
The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters [...] or with two parameters [...] or in some other implementation-defined manner.
"其他一些实现定义的方式"是否包含全局数组? (在我看来,规范仍然指的是一个函数。)
如果没有,它是编译器扩展吗? 或者工具链的一个功能,它可以用于其他目的,他们决定通过前端提供它?
这是因为C允许"非托管"或独立环境,不需要
您在标准中引用的部分是指托管环境,相应的独立部分是:
in a freestanding environment (in which C program execution may take place without any
benefit of an operating system), the name and type of the function called at program
startup are implementation-defined. Any library facilities available to a freestanding
program, other than the minimal set required by clause 4, are implementation-defined.
如果你像往常一样链接它会变坏,因为链接器通常对符号的性质(它有什么类型,甚至它是函数或变量)知之甚少。在这种情况下,链接器将很乐意将对
如果你像往常一样链接它,你基本上是试图在托管操作中使用编译器,然后不定义
the behavior is undefined in the following circumstances:
- ...
- program in a hosted environment does not define a function named
main
using one
of the specified forms (5.1.2.2.1)
独立可能性的目的是能够在没有给出(例如)标准库或CRT初始化的环境中使用C.这意味着可能不会提供在
如果您对如何在主数组中创建程序感兴趣:https://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html。那里的示例源只包含一个名为
主要步骤和问题是:
- 从gdb内存转储中获取主函数的机器指令并将其复制到数组中
-
通过声明const(数据显然是可写的或可执行的)来标记
main[] 可执行文件中的数据 - 最后一个细节:更改实际字符串数据的地址。
生成的C代码就是
1 2 3 4 5 6 | const int main[] = { -443987883, 440, 113408, -1922629632, 4149, 899584, 84869120, 15544, 266023168, 1818576901, 1461743468, 1684828783, -1017312735 }; |
但导致64位PC上的可执行程序:
1 2 3 4 5 6 | $ gcc -Wall final_array.c -o sixth final_array.c:1:11: warning: ‘main’ is usually a function [-Wmain] const int main[] = { ^ $ ./sixth Hello World! |
问题是
GCC给你一个自鸣得意的警告"主要通常是一个函数",暗示使用标识符
愚蠢的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <stdio.h> int main (void) { int main = 5; main: printf("%d ", main); main--; if(main) { goto main; } else { int main (void); main(); } } |
该程序将重复打印数字5,4,3,2,1,直到它出现堆栈溢出并崩溃(不要在家中尝试)。不幸的是,上面的程序是一个严格符合C程序,编译器不能阻止你编写它。
链接器将链接符号
使用gcc,标准入口点是_start,它在准备运行时环境后又调用main()。因此它将跳转到整数数组的地址,这通常会导致错误的指令,段错误或其他一些不良行为。
这一切当然与C标准无关。
它只是编译,因为你没有使用正确的选项(并且因为链接器有时只关心符号的名称,而不是它们的类型)。
1 2 3 4 5 | $ gcc -std=c89 -pedantic -Wall x.c x.c:1:5: warning: ISO C forbids zero-size array ‘main’ [-Wpedantic] int main[0]; ^ x.c:1:5: warning: ‘main’ is usually a function [-Wmain] |
1 | const int main[1] = { 0xc3c3c3c3 }; |
这在x86_64上编译并执行...什么都不做就返回:D