在C语言中,main函数是否必须是一个函数?

14

这段代码可以编译通过,但不出意外地,在链接时失败了(找不到main函数):

代码清单1:

void main();

链接错误:\mingw\lib\libmingw32.a(main.o):main.c:(.text+0x106) 对 _WinMain@16 的引用未定义

但是,下面的代码可以编译和链接成功,并出现警告:

代码清单2:

void (*main)();

警告:'main'通常是一个函数

问题:

  1. 在代码清单1中,链接器应该报告缺少"main"。为什么它在寻找"_WinMain@16"?

  2. 从代码清单2生成的可执行文件会崩溃。原因是什么?

感谢您的时间。


1
它崩溃的原因是解引用 void (*main)(); 执行了一个空指针(如果这是你完整的代码)。 - Mawg says reinstate Monica
8个回答

32

没错,main不一定需要是一个函数。这已经被一些包含名为main的数组中的二进制程序代码的混淆程序所利用。

main()的返回类型必须是int(而不是void)。如果链接器正在寻找WinMain,它会认为你有一个图形用户界面应用程序。


6
在大多数C编译系统中,链接的符号没有与类型信息相关联。例如,您可以将main声明为:
char main[10];

链接器会非常愉快。正如你所指出的,如果你巧妙地初始化数组的内容,程序可能会崩溃。

你的第一个例子没有定义main函数,只是声明了它,因此会出现链接错误。

第二个例子定义了main函数,但是不正确。


第二个例子定义了main,但是不正确。这是否意味着编译器为此代码分配了一些存储空间?void (*main)();这不仅仅是一个声明吗?main指向一个参数未指定且返回值为空的函数(与定义相对)? - Gowtham
1
+1 给这篇帖子。你能举个例子,将 main 声明为一个数组,代码仍然可以正常工作吗?我很好奇这个问题。我从未遇到过这样的代码。 - Jay
1
在过去,这样的事情更容易做到。有关可执行数组的示例,请参见http://stackoverflow.com/questions/2251859/cant-find-my-syntax-error-vc-says-theres-one/2251892#2251892。 - Richard Pennington
2
int main[] = { 0xC3 }; 的意思是定义了一个整型数组 main,其中只有一个元素为 0xC3。0x63 是 RET(返回)的操作码。令人惊讶的是,这似乎会返回 1!不确定这个 1 是从哪里来的。;) - Gowtham

5

情况1:与Windows有关 - 当正确定义main时,编译器可能会生成_WinMain符号。

情况2:您有一个指针,但作为静态变量它被初始化为零,因此导致崩溃。


1
编译器通常在开发Windows应用程序时需要'_WinMain'。许多编译器都有一个控制台模板,需要main,但不提供所有窗口好处,只是老式的C。 - Thomas Matthews
Thomas,谢谢,我知道这一点。只是不确定VS是否会自动将_WinMain“植入”到控制台程序中。 - Nikolai Fetissov

3
在Windows平台上,如果您没有将程序设置为控制台应用程序,则程序的主要单元是WinMain。 "@16"表示它希望有16个字节的参数。因此,只要您提供一个名为WinMain且具有16个字节参数的函数,链接器就会对您感到非常满意。
如果您想要一个控制台应用程序,那么这表明您弄错了什么。

2
你声明了一个名为main的函数指针,链接器警告你这样做行不通。
_WinMain消息与Windows程序的工作方式有关。在C运行时库的下面,Windows可执行文件具有WinMain。

1

3
主函数不需要带参数。int main(void)也是一个完全有效的声明。然而,在C语言中(不同于C++),int main()表示它可以接受任何参数,这是一种不好的风格,但据我所知仍然是合法的。 - Tronic
同意(抱歉),请参考http://en.wikipedia.org/wiki/Main_function_%28programming%29#C_and_C.2B.2B。但是,无论如何,显然他有一个链接错误,因为没有声明主函数(同意吗?)。某些环境(尤其是用于开发基于图形界面的程序的环境)会自动提供这个。 - Mawg says reinstate Monica

0
在代码清单1中,你说“我的代码中有一个main()函数的定义 - 我保证!”。这就是为什么它能够编译通过。但是你在那里撒谎,所以链接失败了。你得到"missing WinMain16"错误的原因是,标准库(对于 Microsoft 编译器)包含一个对main()的定义,它调用WinMain()。在一个Win32程序中,你会定义WinMain(),而连接器会使用库版本的main()来调用WinMain()。
在代码清单2中,你定义了一个名为main的符号,所以编译器和链接器都很高兴,但是启动代码将尝试调用位于“main”位置上的函数,并发现那里实际上没有函数,从而导致崩溃。

这是否意味着编译器为此代码分配了一些存储空间?void (*main)(); 这不仅仅是一个声明吗,main指向一个接受未指定数量的参数并返回空值的函数吗?(与定义相对)。 - Gowtham
不,它为一个指向参数数量未指定且返回值为空的函数的指针分配空间。char* main;甚至void *main;都可以达到同样的效果。 - James Curran

0

1.) 在执行主函数代码之前调用了一个(编译器/平台)相关的函数,因此您的行为是(在Linux/glibc中是_init)。
2) 第二种情况下的代码崩溃是合理的,因为系统无法将符号main的内容作为实际上是指向任意位置的函数指针的函数进行访问。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接