main函数声明
main函数声明
背景:
main函数经常会声明为以下方式:
int main(); int main(int argc, char* argv[]); int main(int argc, char* argv[], char*envp[]);
还有些会将返回类型替换为void,最常见的就是
void main();
一、VC如何支持这些不同的main函数声明
main函数是在__tmainCRTStartup中被调用的,__tmainCRTStartup也没有做特别处理。
不同main函数声明被支持是因为main函数是使用__cdecl调用约定的。
__cdecl调用约定由调用方负责参数的压栈和出栈。
__tmainCRTStartup实现在crtexe.c中,其调用main函数的汇编码如下:
mainret = main(argc, argv, envp); 00A4BAEF mov eax,dword ptr [envp (0A572B0h)] 00A4BAF4 push eax 00A4BAF5 mov ecx,dword ptr [argv (0A572B4h)] 00A4BAFB push ecx 00A4BAFC mov edx,dword ptr [argc (0A572ACh)] 00A4BB02 push edx 00A4BB03 call @ILT+1355(_main) (0A41550h) 00A4BB08 add esp,0Ch 00A4BB0B mov dword ptr [mainret (0A572C4h)],eax
__tmainCRTStartup做如下处理:
1、按从右到左的顺序将三个参数压栈
2、调用main函数
3、出栈 (简单的将栈指针移上来,上移是因为栈是向下生长的)
4、将存放在eax中的返回值赋给int型的mainret
以下声明也是合法的:
int main(int a, int b, char* c, int d, char* e);
PS:关于为什么__tmainCRTStartup调用的main原型与声明的不一致也能通过编译和链接是因为__tmainCRTStartup已经是编译过的,对于函数原型的检查是在编译期执行的,链接时只需要VC链接器能找到main函数符号即可。
二、void替换int,有什么影响
有如下main函数
void main() { printf("Hello, world!\n"); }
其汇编代码如下:
printf("Hello, world!\n"); 00A4D70E mov esi,esp 00A4D710 push offset string "Hello, world!\n" (0A53894h) 00A4D715 call dword ptr [__imp__printf (0A586FCh)] 00A4D71B add esp,4 00A4D71E cmp esi,esp 00A4D720 call @ILT+1335(__RTC_CheckEsp) (0A4153Ch) } 00A4D725 xor eax,eax 00A4D727 pop edi 00A4D728 pop esi 00A4D729 pop ebx 00A4D72A add esp,0C0h 00A4D730 cmp ebp,esp 00A4D732 call @ILT+1335(__RTC_CheckEsp) (0A4153Ch) 00A4D737 mov esp,ebp 00A4D739 pop ebp 00A4D73A ret
编译器会补上xor eax,eax ,返回值放在eax中。
也就是说void替换int后,该函数返回值就是0,不能返回其它值了。其他代码使用GetExitCodeProcess()获取该进程的退出代码时,只能得到0,不具备指示意义。
It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity. We had everything before us, we had nothing before us.
这是最好的时代,这是最坏的时代;这是智慧的年代,这是愚蠢的年代;这是信仰的时期,这是怀疑的时期;我们的前途拥有一切,我们的前途一无所有。
这是最好的时代,这是最坏的时代;这是智慧的年代,这是愚蠢的年代;这是信仰的时期,这是怀疑的时期;我们的前途拥有一切,我们的前途一无所有。