main函数
来源:互联网 发布:sql server error 编辑:程序博客网 时间:2024/06/06 01:00
C++
(1)
之前在调试exe时感觉很奇怪,为什么Entry Point并非直接进入到main函数。
举例来说,如果将一段空的C代码build为exe:
- void main(){ }
编译环境为:VC6 release。
再将该exe文件进行反汇编,那么从EP开始的代码部分大概形如:
……
一段汇编代码
……
call Main函数
……
另一段汇编代码
……
retn
也就是说, 在执行一个exe文件时,总是要先运行一些指令,才能够开始调用Main函数。同样,当main函数执行完毕后,还需要运行一些指令完成收尾。为了弄清楚main函数调用前这些代码以及main函数执行后的代码,需要从CRT(C RunTime ,C的运行时库)开始研究。
(2)
Visual Studio自带了CRT的源码,VC6中CRT位于“VC98\CRT\SRC”目录。CRT 中的 crt0.c 文件规定了一整套C程序固定的执行流程。在 crt0.c 开头的注释部分有如下描述:
大概意思是,当C Run-Time Library 完成了初始化工作之后,才开始执行用户自定义的main 函数、WinMain 函数。
crt0.c 为了规定C程序执行的流程,定义了函数mainCRTStartup 和WinMainCRTStartup ,这两个函数也被称作启动函数 。它们的作用在函数注释中已经写的很清楚:
mainCRTStartup函数大概形如:
- void mainCRTStartup(void){
- int mainret;
- ……
- __try {
- ……
- mainret = main(__argc, __argv, _environ); //在这里调用用户写的main函数
- exit(mainret);
- }
- __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
- {
- _exit( GetExceptionCode() );
- }
- }
当Windows系统执行一个C程序时,真正首先执行的是(win)mainCRTStartup函数。mainCRTStartup首先进行了一系列准备工作,例如heap的初始化、IO的初始化、获得命令行参数等等。当所有的准备工作都完成之后,再去调用用户自定义的main函数。最后,执行exit函数退出程序。因此对于exe,(win)mainCRTStartup函数才是真正的Entry point。
另外,crt0.c 中还有相似的函数:wWinMainCRTStartup、wmainCRTStartup,它们是Unicode版本程序的EP,这里可以暂时不用去管。windows为了照顾Unicode程序,很多API都提供了两种版本,一种是针对ANSI字符,还有一种是针对Unicode字符。
这四个函数是放在一起定义的,crt0.c 中的源码如下:
- #ifdef _WINMAIN_ /* _WINMAIN_被定义时,表示GUI程序 */
- #ifdef WPRFLAG /* WPRFLAG被定义时,表示Unicode字符 */
- void wWinMainCRTStartup(
- #else
- void WinMainCRTStartup(
- #endif
- #else /* 下面为CUI程序 */
- #ifdef WPRFLAG
- void wmainCRTStartup(
- #else
- void mainCRTStartup(
- #endif
- #endif
- void){
- ……
- }
可以根据上面的源代码总结如下:
mainCRTStartupConsole appsANSIwmainCRTStartupConsole appsUnicodeWinMainCRTStartupWindows appsANSIwWinMainCRTStartupWindows appsUnicode
(3)
来具体看一下(win)mainCRTStartup函数。下面将mainCRTStartup函数的主要语句摘录了出来,这里去除了一些条件编译的代码,忽略了windows apps(_WINMAIN_)、Unicode版本的程序(WPRFLAG)、多线程(_MT),仅仅分析Console apps。
- int mainret;
- // 获取Win32的版本
- _osver = GetVersion();
- _winminor = (_osver >> 8) & 0x00FF ;
- _winmajor = _osver & 0x00FF ;
- _winver = (_winmajor << 8) + _winminor;
- _osver = (_osver >> 16) & 0x00FFFF ;
- // 创建了一个属于该进程的私有堆
- if ( !_heap_init(0) )
- fast_error_exit(_RT_HEAPINIT);
- __try {
- // 初始化低级IO
- _ioinit();
- // 获取命令行缓冲区指针
- _acmdln = (char *)GetCommandLineA();
- // 获取环境变量指针
- _aenvptr = (char *)__crtGetEnvironmentStringsA();
- // 设置argv参数
- _setargv();
- // 设置环境变量
- _setenvp();
- // 初始化C数据
- _cinit();
- __initenv = _environ;
- // 调用main函数
- mainret = main(__argc, __argv, _environ);
- exit(mainret);
- }
- __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
- {
- _exit( GetExceptionCode() );
- }
从这段代码可以大体窥视出 C 程序的启动流程:
1.获取WIN32平台的版本
2.调用_heap_init函数创建一个私有堆
3.初始化低级IO
4.获取命令行缓冲区、环境变量的指针
5.设置命令行参数与环境变量
6.初始化C数据
7.调用main函数
8.将main函数的调用结果传入exit 退出程序
GetVersion
GetVersion函数是kernel32.dll中提供的API,用于获取当前Win平台的版本。准确的说,GetVersion可以获得3个信息:
1. OSPlatformId
2. OSBuildNumber
3. OSMinorVersion
4. OSMajorVersion
其中比较诡异的是OSPlatformId,在GetVersion的过程当中它被获得过,但是返回的时候又没了...所以只剩下2、3、4
可以用OD来跟进GetVersion:
- mov eax, dword ptr fs:[18]
- mov ecx, dword ptr [eax+30]
- mov eax, dword ptr [ecx+B0] // 获取 OSPlatformId
- movzx edx, word ptr [ecx+AC] // 获取 OSBuildNumber
- xor eax, FFFFFFFE
- shl eax, 0E // 几次左移,把OSPlatformId的信息给移没了...
- or eax, edx
- shl eax, 8
- or eax, dword ptr [ecx+A8] // 获取 OSMinorVersion
- shl eax, 8
- or eax, dword ptr [ecx+A4] // 获取 OSMajorVersion
- retn
GetVersion主要是去PEB(Process Environment Block)结构中访问当前的OS信息,每个进程都会有自己独立的PEB。想要获取当前进程的PEB地址,首先要先访问TEB(Thread Environment Block )结构。因为TEB结构的30偏移量处中存放了PEB结构的指针。FS 寄存器指向了当前活动线程的TEB结构,其中偏移位置18表示了FS 段寄存器在内存中的镜像地址。
004 线程堆栈顶部
008 线程堆栈底部
00C SubSystemTib
010 FiberData
014 ArbitraryUserPointer
018 FS段寄存器在内存中的镜像地址
020 进程PID
024 线程ID
02C 指向线程局部存储指针
030 PEB结构地址(进程结构)
034 上个错误号
因此
mov eax, dword ptr fs:[18]
mov ecx, dword ptr [eax+30]
两句表示根据TEB获取PEB的指针,并且存放在ECX中。拿到PEB的指针后,就可以去PEB中拿OS的信息。
PEB结构中关于OS信息的偏移量如下:
……
/*0A4*/ ULONG OSMajorVersion;
/*0A8*/ ULONG OSMinorVersion;
/*0AC*/ USHORT OSBuildNumber;
/*0AE*/ USHORT OSCSDVersion;
/*0B0*/ ULONG OSPlatformId;
……
如果是XP用户,那么GetVersion最终的返回值应该是:0A280105 。
其中 0A28表示XP build版本是2600,01表示OSMinorVersion,05表示OSMajorVersion 。
中间被忽略掉的 OSPlatformId 信息可以为:
VER_PLATFORM_WIN32s 或 0x0000,用于指定 Microsoft Windows 3.1。
VER_PLATFORM_WIN32_WINDOWS 或 0x0001,用于指定 Windows 95、Windows 98 或从其继承的操作系统。
VER_PLATFORM_WIN32_NT 或 0x0010,用于指定 Windows NT 或从其继承的操作系统。
另外,还有一些补充的OS信息如下:
(摘录自 http://msdn.microsoft.com/zh-cn/library/ms724833%28v=VS.85%29.aspx )
Operating systemVersion numberdwMajorVersiondwMinorVersionWindows 76.161Windows Server 2008 R26.161Windows Server 20086.060Windows Vista6.060Windows Server 2003 R25.252Windows Home Server5.252Windows Server 20035.252Windows XP Professional x64 Edition5.252Windows XP5.151Windows 20005.050------------- from http://driftcloudy.iteye.com/blog/1048750----------------------
Java
public static void main(String[] args) {}
但是以前一直都没有问自己,为什么要这么写,因为在c语言中就没有这样子的要求。其实这是一个不需要解释的问题,因为java标准就是这么规定的,那么既然是java标准规定的,我们按照规定来执行就好了。不过,这并不是一个很好的学习态度,如果总是知其然而不知其所以然,总会对java有种隔膜的感觉。就是发现问题了,不去解决,不去了解为什么,心里总是会有牵绊。今天既然自己都这么问自己了,为什么java的主函数要按照这个格式来写,那么我就得弄明白为什么。
在java中,main()方法是java应用程序的入口方法。java虚拟机通过main方法找到需要启动的运行程序,并且检查main函数所在类是否被java虚拟机装载。如果没有装载,那么就装载该类,并且装载所有相关的其他类。因此程序在运行的时候,第一个执行的方法就是main()方法。通常情况下, 如果要运行一个类的方法,必须首先实例化出来这个类的一个对象,然后通过"对象名.方法名()"的方式来运行方法,但是因为main是程序的入口,这时候还没有实例化对象,因此将main方法声明为static的,这样这个方法就可以直接通过“类名.方法名()”的方式来调用。
实例
虚拟机通过调用某个指定类的方法main启动,传递给main一个字符串数组参数,使指定的类被装载,同时链接该类所使用的其它的类型,并且初始化它们。例如对于程序:
public class HelloApp { public static void main(String[] args) { System.out.println("Hello World!"); for (int i = 0; i < args.length; i++) { System.out.println(args); } }}
编译后在命令行模式下键入: java HelloApp run virtual machine
将通过调用HelloApp的方法main来启动java虚拟机,传递给main一个包含三个字符串"run"、"virtual"、"machine"的数组。现在我们略述虚拟机在执行HelloApp时可能采取的步骤。
开始试图执行类HelloApp的main方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进制代表,于是虚拟机使用ClassLoader试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。类被装载后同时在main方法被调用之前,必须对类HelloApp与其它类型进行链接然后初始化。链接包含三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以及把这些域初始化为标准的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。类的初始化是对类中声明的静态初始化函数和静态域的初始化构造方法的执行。一个类在初始化之前它的父类必须被初始化。整个过程如下:
* Java中的main()方法详解
*/
public class HelloWorld {
public static void main(String args[]) {
System.out.println("Hello World!");
}
}
public static void main(String[] args) throws Exception {
System.out.println("哈哈哈哈哈");
throw new Exception("");
}
}
Exception in thread "main" java.lang.Exception:
at maintest.TestMain.main(TestMain.java:11)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
Process finished with exit code 1
* 打印main方法中的输入参数
*/
public class TestMain {
public static void main(String args[]){
System.out.println("打印main方法中的输入参数!");
for(int i=0;i<args.length;i++){
System.out.println(args[i]);
}
}
}
D:\Study\basetest\src>java TestMain 1 2 3
打印main方法中的输入参数!
1
2
3
* 变态版的HelloWorld.呵呵
*/
public class HelloWorld2 {
static {
System.out.println("Hello Wordld!");
}
public static void main(String args[]){
System.exit(0);
}
}
- main()函数
- main函数
- main函数
- main函数
- main函数
- main函数
- main()函数
- main函数
- main函数
- main()函数
- main函数
- main函数
- main函数
- main函数
- Main 函数
- Main函数
- Main函数
- main函数
- VS改大小写的快捷键
- SOA与ESB的关系
- java工具类 获取包下所有类
- Cocos2d-x教程 CocoStudio篇 (3) UICheckBox 复选框控件
- 数据库导出CSV格式,并压缩成ZIP的形式导出
- main函数
- BANK_BASE(i)的理解 phys_addr_t
- LIBUSB 介绍
- C#使用GDAL计算某点的高程值
- explicit关键字的作用
- 持续集成 最佳实践 研讨会(1月25日 广州)
- hadoop配置过程中出错的地方
- Makefile详解和实例
- C#--工作笔记(直方图Page页)