main函数执行以前

现在最重要的是要跟得上潮流,所以套用比较时髦的话,谁动了我的奶酪。谁调用了我的 main?不过作为计算机工作者,我劝大家还是不要赶时髦,今天Java热,明天 .net 流行,什么时髦就学什么。我的意思是先花几年把基本功学好,等你赶时髦的时候也好事半功倍。废话不多说了。
    
    我们都听说过一句话:“main是C语言的入口”。我至今不明白为什么这么说。就好像如果有人说:“挣钱是泡妞”,肯定无数砖头拍过来。这句话应该是“挣钱是泡妞的一个条件,只不过这个条件特别重要”。那么上面那句话应该是 “main是C语言中一个符号,只不过这个符号比较特别。”
    
    我们看下面的例子:
    
    /* file name test00.c */
    
    int main(int argc, char* argv)
    {
     return 0;
    }
    
    编译链接它:
    cc test00.c -o test.exe
    会生成 test.exe
    
    但是我们加上这个选项: -nostdlib (不链接标准库)
    cc test00.c -nostdlib -o test.exe
    链接器会报错:
    undefined symbol: __start
    
    也就是说:
    1. 编译器缺省是找 __start 符号,而不是 main
    2. __start 这个符号是程序的起始点
    3. main 是被标准库调用的一个符号
    
    再来思考一个问题:
    我们写程序,比如一个模块,通常要有 initialize 和 de-initialize,但是我们写 C 程序的时候为什么有些模块没有这两个过程么呢?比如我们程序从 main 开始就可以 malloc,free,但是我们在 main 里面却没有初始化堆。再比如在 main 里面可以直接 printf,可是我们并没有打开标准输出文件啊。(不知道什么是 stdin,stdout,stderr 以及 printf 和 stdout 关系的群众请先看看 C 语言中文件的概念)。
    
    有人说,这些东西不需要初始化。如果您真得这么想,请您不要再往下看了,我个人认为计算机软件不适合您。
    
    聪明的人民群众会想,一定是在 main 之前干了些什么。使这些函数可以直接调用而不用初始化。通常,我们会在编译器的环境中找到一个名字类似于 crt0.o 的文件,这个文件中包含了我们刚才所说的 __start 符号。(crt 大概是 C Runtime 的缩写,请大家帮助确认一下。)
    
    那么真正的 crt0.s 是什么样子呢?下面我们给出部分伪代码:
    
    ///////////////////////////////////////////////////////
    section .text:
    __start:
    
     :
     init stack;
     init heap;
     open stdin;
     open stdout;
     open stderr;
     :
     push argv;
     push argc;
     call _main; (调用 main)
     :
     destory heap;
     close stdin;
     close stdout;
     close stderr;
     :
     call __exit;
    ////////////////////////////////////////////////////
    
    实际上可能还有很多初始化工作,因为都是和操作系统相关的,笔者就不一一列出了。
    
    注意:
    1. 不同的编译器,不一定缺省得符号都是 __start。
    2. 汇编里面的 _main 就是 C 语言里面的 main,是因为汇编器和C编译器对符号的命名有差异(通常是差一个下划线'_')。
    3. 目前操作系统结构有两个主要的分支:微内核和宏内核。微内核的优点是,结构清晰,简单,内核组件较少,便于维护;缺点是,进程间通信较多,程序频繁进出内核,效率较低。宏内核正好相反。我说这个是什么目的是:没办法保证每个组件都在用户空间(标准库函数)中初始化,有些组件确实可能不要初始化,操作系统在创建进程的时候在内核空间做的。这依赖于操作系统的具体实现,比如堆,宏内核结构可能在内核初始化,微内核结构在用户空间;即使同样是微内核,这个东东也可能会被拿到内核空间初始化。
    
    随着 CPU 技术的发展,存储量的迅速扩展,代码复杂程度的增加,微内核被越来越多的采用。你会为了 10% 的效率使代码复杂度增加么?要知道每隔 18 个月 CPU 的速度就会翻一番。所以我对程序员的要求是,我首先不要你的代码效率高,我首先要你的代码能让 80% 的人迅速看懂并可以维护。

总结:

main函数执行之前,主要就是初始化系统相关资源:

1.设置栈指针

2.初始化static静态和global全局变量,即data段的内容

3.将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容

4.运行全局构造器,估计是C++中构造函数之类的吧

5.将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数
### 三、C++是否具备类型安全性? C++ 并不是完全类型安全的语言。虽然它在编译时提供了较强的类型检查机制,但在运行时仍存在绕过类型系统的方式,例如强制类型转换(`reinterpret_cast`、`static_cast`)、指针操作、联合体(`union`)等。这些机制在提供灵活性和性能优化的同时,也增加了类型不安全的风险。 例如,通过指针可以将 `int*` 强制转换为 `float*` 并进行访问,这可能导致未定义行为[^1]。 ```cpp int a = 10; float* pf = reinterpret_cast<float*>(&a); std::cout << *pf; // 类型不安全的访问 ``` 因此,C++ 在设计上更偏向于“类型安全但可绕过”,以支持底层系统编程和性能优化需求。 --- ### 四、C++中函数参数传递的方式及其区别 C++ 中函数参数的传递方式主要有三种:**值传递**、**指针传递**和**引用传递**。 #### 1. 值传递(Pass by Value) 函数接收的是实参的副本,对形参的修改不会影响实参。 ```cpp void func(int x) { x = 100; } int a = 5; func(a); // a 的值仍为 5 ``` #### 2. 指针传递(Pass by Pointer) 函数接收的是指向实参的指针,通过解引用可以修改实参的值。 ```cpp void func(int* x) { *x = 100; } int a = 5; func(&a); // a 的值变为 100 ``` #### 3. 引用传递(Pass by Reference) 函数接收的是实参的别名,对形参的操作直接作用于实参。 ```cpp void func(int& x) { x = 100; } int a = 5; func(a); // a 的值变为 100 ``` 值传递适用于不需要修改实参的情况,指针和引用传递适用于需要修改实参或处理大型对象以避免拷贝开销的情况。 --- ### 五、为什么数组名作为函数参数会修改原数组内容,而int等基本类型不会? 数组名作为函数参数时,其本质是一个指向数组首元素的指针。因此,函数内部对数组的操作实际上是对原始内存地址的访问和修改,从而影响原数组内容[^1]。 ```cpp void func(int arr[]) { arr[0] = 100; } int a[5] = {1, 2, 3, 4, 5}; func(a); std::cout << a[0]; // 输出 100 ``` 而 `int` 等基本类型默认是值传递,函数接收的是副本,修改不会影响原始变量。若要修改原始变量,需使用指针或引用传递。 --- ### 六、main函数执行前会调用哪些函数? 在 `main` 函数执行之前,程序会自动执行全局对象和静态对象的构造函数,以及使用 `__attribute__((constructor))`(在 GCC/Clang 中)或 `#pragma init_seg`(在 MSVC 中)声明的函数。这些机制常用于初始化全局资源或注册模块。 ```cpp #include <iostream> struct Init { Init() { std::cout << "Global init" << std::endl; } }; Init globalInit; // main 执行前构造 int main() { std::cout << "Main" << std::endl; return 0; } ``` 输出: ``` Global init Main ``` 此外,C++ 标准库的初始化、线程局部存储(TLS)初始化等也在 `main` 执行前完成。 --- ### 七、static函数与普通函数的区别 静态函数(`static`)的作用域仅限于定义它的翻译单元(源文件),不能被其他文件访问,具有内部链接性。普通函数具有外部链接性,可以在其他文件中通过声明调用。 例如: ```cpp // file1.cpp static void helper() { /* 只能在 file1.cpp 中访问 */ } void normalFunc() { /* 可以被其他文件访问 */ } ``` 静态函数常用于实现模块内部的辅助函数,避免命名冲突和不必要的暴露[^3]。 --- ### 八、C++中类的声明与实现分离的好处 类的声明(通常在 `.h` 或 `.hpp` 文件中)与实现(通常在 `.cpp` 文件中)分离具有以下优势: - **提高编译效率**:修改实现文件时,无需重新编译依赖头文件的其他模块。 - **增强封装性**:隐藏实现细节,只暴露接口,提升模块化设计。 - **便于维护和协作**:多个开发者可以并行开发不同模块,减少冲突。 - **支持接口与实现解耦**:便于后期重构实现而不影响接口使用者。 例如: ```cpp // MyClass.h class MyClass { public: void doSomething(); }; // MyClass.cpp #include "MyClass.h" void MyClass::doSomething() { // 实现细节 } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值