2016-01-13 97 views
44

我看到a snippet of code on CodeGolf这是一个编译器炸弹,其中main被声明为一个巨大的数组。我尝试以下(无弹)版本:为什么将main声明为数组编译?

int main[1] = { 0 }; 

这似乎编译罚款锵之下,只有在GCC警告:

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

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

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

5.1.2.2.1计划启动

称为在程序启动的功能被命名为主力。该实现没有声明这个函数的原型。它应该用int类型的返回类型定义,并且不带任何参数或者带有两个参数,或者以某种其他实现定义的方式定义。

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

如果不是,它是一个编译器扩展吗?或者是工具链的一个特征,用于其他目的,并且他们决定通过前端使其可用。

+1

它**不**编译。 ISO C禁止零大小的数组。 – Jens

+7

C规范不允许。编译器通常会执行规范未涵盖的内容。 –

+0

相关问题:[一个全局变量的程序如何调用main而不是主函数?](http://stackoverflow.com/q/32851184/1708801)。我想也受到了一个codegolf问题的启发。 –

回答

30

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

你指的是在标准是指宿主环境中的部分中,独立式的对应是:

在独立环境中

(其中C程序执行可发生而无需操作系统的任何 益处),程序 启动时调用的函数的名称和类型是实现定义的。除了第4条所要求的最低限度设置以外,独立式 程序可用的任何图书馆设施都是实施定义的。

如果然后将其链接像往常一样,将走坏,因为链接器通常具有对符号的本质知之甚少(它有什么类型的,甚至如果它是一个函数或变量)。在这种情况下,链接器将愉快地将对main的调用解析为名为main的变量。如果找不到符号,将导致链接错误。

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

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

  • ...
  • 计划在托管环境中没有定义使用的特定形式之一 命名 主要 功能(5.1.2.2.1)

自支撑possibil的目的ity可以在没有给出标准库或CRT初始化的环境中使用C.这意味着,在main之前运行的代码被称为(这是CRT初始化初始化C运行时)可能不提供,你预计将提供自己(和你可能决定一个main或可决定不) 。

+0

这个编译和链接罚款(以及警告)与gcc 4.9.3上cygwin:'int f(int argc,char ** argv) { \t return 0; } char * main =(char *)f;' –

+0

@ PeterA.Schneider但是,如果它运行良好,它只是纯粹的运气。 CRT-init会尝试调用'main',这是指针存储的地方,而不是它指向的地方。 – skyking

+0

它链接,但段错误。顺便说一句,我不认为这个问题与“独立”有很大关系。例如,下面的代码在VS13中编译和链接(到一个dll):''''''''''''''''''main main = 0; } } '。这是相当主要(和主要在C#中)不是关键字,C连接器是愚蠢的,错误,简单。 –

7

main是 - 在许多人一样(全局函数,全局变量等)的对象文件只是一个符号 - 编译后。

连接器将连接符号main无论其类型。事实上,链接器无法看到所有符号的类型(他可以看到,这是不是在.text -section然而,但他并不在乎;))

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

这一切当然是无关与C标准。

+0

我作为skyking的回复链接发表评论的最小例子,但segfaults。任何调整,使内联汇编程序或类似的工作呢? –

+0

@ PeterA.Schneider它是segfaults,因为它会跳转到指针的_address_而不是它的内容。 – Ctx

+0

谢谢!我想我仍然期望这些工具链的C前端抛出,即使链接器在看到目标文件时并不关心。 –

2

它只编译,因为你不使用正确的选项(和作品,因为接头有时只关心名称符号,而不是他们)。

$ 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] 
+2

它仍然编译和链接。唯一的区别是它警告你'main'通常是一个函数(然后继续并链接)。 – skyking

+0

@skyking你想编译/链接失败?然后添加'-Werror'。 – Jens

+0

但是其他(其他)有效的C程序也无法编译。 – skyking

20

如果你有兴趣如何创建主阵列方案:https://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html。那里的示例源只包含一个名为main的char(以及后面的int)数组,其中充满了机器指令。

的主要步骤和问题是:

  • 从GDB存储器转储获得一个主要功能的机器指令,并在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! 
5

的问题是,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(); 
    } 
} 

这个程序将反复打印号码5,4,3,2,1,直到它得到一个堆栈溢出和崩溃(不要在家里尝试这个) 。不幸的是,上述程序是一个严格符合C标准的程序,编译器无法阻止您编写它。

相关问题