首页 文章

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

提问于
浏览
53

我看到a snippet of code on CodeGolf用作编译器炸弹,其中 main 被声明为一个巨大的数组 . 我试过以下(非炸弹)版本:

int main[1] = { 0 };

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

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

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

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

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

"some other implementation-defined manner"是否包含全局数组? (在我看来,规范仍然指的是一个函数 . )

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

6 回答

  • 0

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

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

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

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

    如果您基本上尝试在托管操作中使用编译器,然后不定义 main ,因为您应该根据附录J.2表示未定义的行为:

    在以下情况下,行为未定义:...托管环境中的程序未使用其中一个指定表单定义名为main的函数(5.1.2.2.1)

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

  • 23

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

    主要步骤和问题是:

    • 从gdb内存转储中获取主函数的机器指令并将其复制到数组中

    • 通过声明const来标记 main[] 可执行文件中的数据(数据显然是可写的或可执行的)

    • 最后一个细节:更改实际字符串数据的地址 .

    生成的C代码就是

    const int main[] = {
        -443987883, 440, 113408, -1922629632,
        4149, 899584, 84869120, 15544,
        266023168, 1818576901, 1461743468, 1684828783,
        -1017312735
    };
    

    但导致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!
    
  • 36

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

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

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

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

  • 8

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

    海湾合作委员会给你一个自鸣得意的警告"main is usually a function",暗示使用标识符 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();
      }
    }
    

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

  • 8

    它只是编译,因为你没有使用正确的选项(并且因为链接器有时只关心符号的名称,而不是它们的类型) .

    $ 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]
    
  • 3
    const int main[1] = { 0xc3c3c3c3 };
    

    这在x86_64上编译并执行...什么都不做就返回:D

相关问题