为什么声明main作为数组编译?

The*_*kis 56 c gcc program-entry-point clang

在CodeGolf上看到了一段代码,它的目的是作为编译器炸弹,其中main声明为一个巨大的数组.我试过以下(非炸弹)版本:

int main[1] = { 0 };
Run Code Online (Sandbox Code Playgroud)

它似乎在Clang下编译得很好,并且在GCC下只有一个警告:

警告:'main'通常是一个函数[-Wmain]

结果二进制文件当然是垃圾.

但为什么它会编译呢?是否允许C规范?我认为相关的部分说:

5.1.2.2.1程序启动

程序启动时调用的函数名为main.该实现声明此函数没有原型.它应定义为返回类型为int且没有参数[...]或具有两个参数[...]或以某种其他实现定义的方式.

"其他一些实现定义的方式"是否包含全局数组?(在我看来,规范仍然指的是一个函数.)

如果没有,它是编译器扩展吗?或者工具链的一个功能,它可以用于其他目的,他们决定通过前端提供它?

sky*_*ing 39

这是因为C允许"非托管"或独立环境,不需要该main功能.这意味着该名称将main被释放用于其他用途.这就是为什么这样的语言允许这样的声明.大多数编译器都旨在支持两者(差异主要是如何完成链接),因此它们不会禁止在托管环境中非法的构造.

您在标准中引用的部分是指托管环境,相应的独立部分是:

在独立环境中(可以在没有操作系统任何好处的情况下执行C程序),程序启动时调用的函数的名称和类型是实现定义的.除了第4节要求的最小集合之外,任何可用于独立程序的库设施都是实现定义的.

如果你像往常一样链接它会变坏,因为链接器通常对符号的性质(它有什么类型,甚至它是函数或变量)知之甚少.在这种情况下,链接器将很乐意解析对main名为的变量的调用main.如果未找到符号,则会导致链接错误.

如果你像往常一样链接它,你基本上是试图在托管操作中使用编译器,然后不定义,main因为你应该意味着未定义的行为,如附录J.2所示:

在以下情况下,行为未定义:

  • ...
  • 托管环境中的程序没有使用指定的表单之一定义名为main的函数(5.1.2.2.1)

独立可能性的目的是能够在没有给出(例如)标准库或CRT初始化的环境中使用C. 这意味着main可能没有提供之前运行的代码(即初始化C运行时的CRT初始化),并且您可能希望自己提供(并且您可能决定拥有main或可能决定不这样做).


小智 24

如果您对如何在主数组中创建程序感兴趣:https://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html.那里的示例源只包含一个char(以及后来的int)数组main,该数组被机器指令填充.

主要步骤和问题是:

  • 从gdb内存转储中获取主函数的机器指令并将其复制到数组中
  • main[]通过声明const来标记可执行文件中的数据(数据显然是可写的或可执行的)
  • 最后一个细节:更改实际字符串数据的地址.

生成的C代码就是

const int main[] = {
    -443987883, 440, 113408, -1922629632,
    4149, 899584, 84869120, 15544,
    266023168, 1818576901, 1461743468, 1684828783,
    -1017312735
};
Run Code Online (Sandbox Code Playgroud)

但导致64位PC上的可执行程序:

$ 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!
Run Code Online (Sandbox Code Playgroud)


Lun*_*din 9

问题是它main不是保留的标识符.C标准只说在托管系统中通常有一个名为main的函数.但是标准中的任何内容都不会阻止您滥用相同的标识符以用于其他险恶目的.

海湾合作委员会给你一个自鸣得意的警告"主要通常是一个功能",暗示将标识符main用于其他不相关的目的并不是一个好主意.


愚蠢的例子:

#include <stdio.h>

int main (void)
{
  int main = 5;
  main:

  printf("%d\n", main);
  main--;

  if(main)
  {
    goto main;
  }
  else
  {
    int main (void);
    main();
  }
}
Run Code Online (Sandbox Code Playgroud)

该程序将重复打印数字5,4,3,2,1,直到它出现堆栈溢出并崩溃(不要在家中尝试).不幸的是,上面的程序是一个严格符合C程序,编译器不能阻止你编写它.


Ctx*_*Ctx 8

main 是 - 在编译之后 - 在对象文件中只是另一个符号,就像许多其他符号一样(全局函数,全局变量等).

链接器将链接符号,main无论其类型如何.实际上,链接器根本看不到符号的类型(他可以看到,它不在.text-section中,但他并不关心;))

使用gcc,标准入口点是_start,它在准备运行时环境后又调用main().因此它将跳转到整数数组的地址,这通常会导致错误的指令,段错误或其他一些不良行为.

这一切当然与C标准无关.