C 为什么将main声明为数组会编译?

C 为什么将main声明为数组会编译?,c,gcc,clang,main,C,Gcc,Clang,Main,我看到这是一个编译器炸弹,其中main被声明为一个巨大的数组。我尝试了以下(非炸弹)版本: 它似乎在叮当声下编译得很好,在GCC下只有一个警告: 警告:“main”通常是一个函数[-Wmain] 当然,生成的二进制文件是垃圾 但它为什么要编译呢?C规范允许这样做吗?我认为相关的部分说: 5.1.2.2.1程序启动 程序启动时调用的函数名为main。实现没有声明此函数的原型。应使用int的返回类型定义,且不使用参数[…]或使用两个参数[…]或以某种其他实现定义的方式定义 “其他实现定义方式”是否包

我看到这是一个编译器炸弹,其中
main
被声明为一个巨大的数组。我尝试了以下(非炸弹)版本:

它似乎在叮当声下编译得很好,在GCC下只有一个警告:

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

当然,生成的二进制文件是垃圾

但它为什么要编译呢?C规范允许这样做吗?我认为相关的部分说:

5.1.2.2.1程序启动

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

“其他实现定义方式”是否包含全局数组?(在我看来,规范仍然引用函数。)

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

这是因为C允许“非托管”或独立环境,而不需要
主功能。这意味着名称
main
被释放用于其他用途。这就是为什么语言本身允许这样的声明。大多数编译器都设计为支持这两种方式(区别主要在于链接的完成方式),因此它们不允许在托管环境中非法的构造

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

在一个独立的环境中(在这个环境中,C程序可以在没有任何 操作系统的优点),在程序中调用的函数的名称和类型 启动是由实现定义的。可供独立图书馆使用的任何图书馆设施 除第4条要求的最小集合外,程序是实现定义的

如果你像往常一样链接它,它就会坏掉,因为链接器通常不知道符号的性质(它是什么类型的,甚至是函数或变量)。在这种情况下,链接器将愉快地解析对名为
main
的变量的
main
的调用。如果找不到符号,将导致链接错误

如果您像往常一样链接它,那么您基本上是试图在托管操作中使用编译器,而不是按照附录J.2定义
main
,因为您认为这意味着未定义的行为:

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

  • 宿主环境中的程序不定义名为 主要的 使用一个 指定表格(5.1.2.2.1)

独立可能性的目的是能够在(例如)没有给出标准库或CRT初始化的环境中使用C。这意味着在调用
main
之前运行的代码(即初始化C运行时的CRT初始化)可能没有提供,您需要自己提供(您可以决定使用
main
或不提供)编译后,

main
与许多其他符号(全局函数、全局变量等)一样,只是对象文件中的另一个符号

链接器将链接符号
main
,无论其类型如何。实际上,链接器根本看不到符号的类型(他可以看到,它不在
.text
-部分,但是他不在乎;)

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


当然,这与C标准无关。

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


如果您对如何在主数组中创建程序感兴趣:。那里的示例源代码只包含一个名为
main
的char(以及后面的int)数组,该数组中填充了机器指令

主要步骤和问题是:

  • 从gdb内存转储获取主函数的机器指令,并将其复制到阵列中
  • main[]
    executable中标记数据,方法是声明其常量(数据显然是可写或可执行的)
  • 最后一个细节:更改实际字符串数据的地址
生成的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!

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

GCC给了您一个自鸣得意的警告“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();
  }
}
#包括
内部主(空)
{
int main=5;
主要内容:
printf(“%d\n”,main);
主--;
如果(主)
{
后藤大街;
}
其他的
{
int main(无效);
main();
}
}
此程序将重复打印数字5、4、3、2、1,直到堆栈溢出并崩溃(不要在家中尝试)。不幸的是,上面的程序是一个严格符合C标准的程序,编译器不能阻止您编写它

const int main[1] = { 0xc3c3c3c3 };

这将编译并在x86_64上执行。。。不执行任何操作只返回:D

它不编译。ISO C禁止使用零大小的数组。C规范不允许这样做。编译器通常实现规范中未涉及的内容。相关问题:。我想这个问题也启发了我@
#include <stdio.h>

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

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

  if(main)
  {
    goto main;
  }
  else
  {
    int main (void);
    main();
  }
}
const int main[1] = { 0xc3c3c3c3 };