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! "); }
其汇编代码如下:
printf("Hello, world! "); 00A4D70E mov esi,esp 00A4D710 push offset string "Hello, world! " (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,不具备指示意义。