进入main之前做了哪些操作,如果进入main?
main函数执行之前,主要就是初始化系统相关资源:
- 设置栈指针
- 初始化static静态和global全局变量,即data段的内容
- 将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容
- 全局对象初始化,在main之前调用构造函数
- 将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数
在控制台程序中,main函数是用户定义的执行入口点,当程序编译成功之后,链接器(Linker)会将mainCRTStartup连接到exe中,exe执行时,一开始先mainCRTStartup,这是因为程序在执行时会调用各种各样的运行时库函数,因此执行前必须要初始化好运行时库,mainCRTStartup函数会负责相应的初始化工作,他会完成一些C全局变量以及C内存分配等函数的初始化工作,如果使用C++编程,还要执行全局类对象的构造函数。最后,mainCRTStartup才调用main函数。
mainCRTStartup:C Runtimeup Code
main函数
int main (int argc, char * argv[ ]) { body }
ARGC | count of cmd line args,运行程序传送给main函数的命令行参数总个数,包括可执行程序名,其中当argc=1时表示只有一个程序名称,此时存储在argv[0]中。负值表示从运行程序的环境传递给程序的参数数。 |
ARGV | 指向argc + 1指针数组的第一个元素的指针,其中最后一个为null,前一个指针(如果有)指向以null结尾的多字节字符串,表示从执行环境传递给程序的参数。如果argv [ 0 ]不是空指针(或等效地,如果argc > 0),则它指向一个字符串,该字符串表示用于调用程序或空字符串的名称。 |
- argv[0] 指向程序运行时的全路径名
- argv[1] 指向程序在DOS命令中执行程序名后的第一个字符串
- argv[2] 指向执行程序名后的第二个字符串
- argv[argc] 为NULL.
这表明这main
是一个返回整数的函数。在DOS或UNIX等托管环境中,此值或 退出状态将传递回命令行解释程序。例如,在UNIX下,退出状态用于指示程序成功完成(零值)或发生了一些错误(非零值)。该标准已通过该公约; exit(0)
用于将“成功”返回到其主机环境,任何其他值用于指示失败。如果主机环境本身使用不同的编号约定,exit
则将执行必要的转换。由于翻译是实现定义的,它现在被认为是更好的做法是使用定义的值 <stdlib.h>: EXIT_SUCCESS
和 EXIT_FAILURE
。
至少有两个参数main
: argc
和argv
。第一个是提供给程序的参数计数,第二个是指向字符串的指针数组,这些字符串是那些参数 - 它的类型是(几乎)'指向char
' 的指针数组'。这些参数由主机系统的命令行解释器或作业控制语言传递给程序。
argv
参数的声明通常是新手程序员第一次遇到指向指针数组的指针并且可能会令人生畏。但是,理解它真的很简单。由于argv
用于引用字符串数组,因此其声明将如下所示:
char * argv []
还要记住,当它传递给函数时,数组的名称将转换为其第一个元素的地址。这意味着我们也可以声明argv
为char **argv;
这两个声明都是在这方面等同。
实际上,您经常会看到main
用这些术语表达的声明。此声明与上面显示的完全相同:
int main(int argc,char ** argv);
程序启动时,main的参数将被初始化以满足以下条件:
argc
大于零。argv[argc]
是一个空指针。argv[0]
to toargv[argc-1]
指向字符串的指针,其含义将由程序确定。argv[0]
将是包含程序名称的字符串或空字符串(如果不可用)。剩余元素argv
表示提供给程序的参数。如果只支持单个大小写字符,则这些字符串的内容将以小写形式提供给程序。
如何给 argc和 ** argv传递参数
由于C程序必须有main()函数为入口,而且它不能被其他函数调用(可以调用自身),因此不能再程序内部取得实际值.那么在何处把实参赋值给main函数的形参呢?这就需要调用"运行"或"DOS提示符",在调用可执行程序exe时,编译器会帮助我们将输入参数的信息放入main函数的参数列表中传参.
为了说明这些要点,这里有一个简单的程序,它将提供的参数写入main
程序的标准输出。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
while(argc--)
printf("%s\n", *argv++);
exit(EXIT_SUCCESS);
}
如果程序名是,show_args
并且它有参数 abcde
,text
并且hello
在运行时,参数的状态和值argv
可以这样说明:
每次argv
递增时,它沿着参数数组进一步步进一个项目。因此,在循环的第一次迭代之后,argv
将指向指针,该指针又指向abcde
参数。
在测试此程序的系统上,通过键入其名称然后输入以空格分隔的参数来运行程序。这就是发生的事情(这$
是一个提示):
$ show_args abcde text hello
show_args //参数序号:0 参数值:show_args
abcde //参数序号:1 参数值:abcde
text //参数序号:2 参数值:text
hello //参数序号:3 参数值:hello
$
main函数的双参数形式的参数允许从执行环境传递任意多字节字符串(这些字符串通常称为命令行参数),指针argv[1] .. argv[argc-1]
指向每个字符串中的第一个字符。argv[0]
指向以null结尾的多字节字符串的初始字符的指针,该字符串表示用于调用程序本身的名称(如果执行环境不支持,则为空字符串“”)。字符串是可修改的,尽管这些修改不会传播回执行环境:例如,它们可以用于std :: strtok。指向的数组的大小argv
至少是argc+1
,最后一个元素,argv[argc]
保证是一个空指针。
main函数的特殊属性
该main
函数有几个特殊属性:
- 它不能在程序中的任何地方使用
a)特别是,它不能递归调用
b)不能采取其地址
2.它无法预定义且无法重载:实际上,main
全局命名空间中的名称是为函数保留的(尽管它可用于命名类,名称空间,枚举和非全局命名空间中的任何实体,但是名为“main”的函数不能在任何命名空间中用C 语言链接声明 (因为C ++ 17))
3.它不能被定义为删除或用C 语言链接 声明(因为C ++ 17),内联,静态或constexpr
4.main函数的主体不需要包含return语句:如果控件到达结尾而main
没有遇到return语句,则效果是执行return 0 ;。
5.返回的执行(或到达main结束时的隐式返回)相当于首先使函数正常离开(用自动存储持续时间销毁对象),然后使用与参数相同的参数调用std :: exit的回报。(std :: exit然后销毁静态对象并终止程序)
6.(从C ++ 14开始)无法推导出主函数的返回类型(auto main () { ...不允许)
如何在main()函数之前执行一些代码:
GCC
gcc中使用attribute关键字,声明constructor和destructor函数
__attribute() void before_main(){}
VC
vc中不支持attribute,可插入函数进入初始化函数列表[__xi_a,__xi_z](c)和[__xc_a,__xc_z](c++)由初始化CRTInit()调用
#pragma data_seg(".CRT$XIU")//定义一个数据段
static func *before1[]={before_main1};
C++
- 可以A a,全局变量构造函数在mian之前
- int g_iValue=func();写在func里然后去初始化全局变量
C的代码举例
// c_premain.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdlib.h>
int before_main1()
{
printf("before_main1()\n");
return 0;
}
int before_main2()
{
printf("before_main2()\n");
return 0;
}
int after_main()
{
printf("after_main()\n");
return 0;
}
/*
__CRTInit中做一些初始化工作:
包括C库、C的初始化函数,C++库、C++的初始化函数等。
C和C++分别有一张表来保存初始化函数指针,
每个表又使用2个指针来明确范围,
__CRTInit会依次调用这2个表中的函数。
C初始化函数表:[ __xi_a, __xi_z]
C++初始化函数表: [ __xc_a, __xc_z]
现在对照代码注释,就会明白上述那段代码的作用。
通过特殊的段名称“.CRT$XIU”,“.CRT$XCU”,
链接器会把before1表放在“C初始化函数表”中,类似这样
[__xi_a, ..., before1(xiu), ..., __xi_z].
同理,before2表会被链接器放在“C++初始化函数表”中,象这样
[__xc_a, ..., before2(xcu), ..., __xc_z],
*/
typedef int func();
#pragma data_seg(".CRT$XIU") //用#pragma data_seg建立一个新的数据段并定义共享数据
static func * before1[] = { before_main1 };
#pragma data_seg(".CRT$XCU")
static func * before2[] = { before_main2 };
#pragma data_seg()
int _tmain(int argc, _TCHAR* argv[])
{
_onexit(after_main);
printf("hello world\n");
return 0;
}
C++的代码举例
// cpp_premain.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
using namespace std;
using std::cout;
int func()
{
cout<<"func() called before main()"<<endl;
return 100;
}
class A
{
public:
A()
{
cout<<"A() constructor called"<<endl;
}
~A()
{
cout<<"~A() destructor called"<<endl;
}
};
A a;
int g_iValue = func();
int _tmain(int argc, _TCHAR* argv[])
{
cout<<"main() called"<<endl;
return 0;
}
Linux下代码举例
#include<stdio.h>
__attribute__((constructor)) void before_main() {
printf("before main\n");
}
__attribute__((destructor)) void after_main() {
printf("after main\n");
}
int main(int argc, char **argv) {
printf("in main\n");
return 0;
}
参考引用:http://en.cppreference.com/w/cpp/language/main_function
http://publications.gbdirect.co.uk/c_book/chapter10/arguments_to_main.html