再读C++的设计艺术(3)

Posted by Johan Niu on April 8, 2018

再读C++的设计艺术(3)

1 程序运行过程

程序是如何运行的?点击一个exe,实在操作系统上运行一个程序,操作系统将exe的一些有效文件从磁盘装载到内存中。

对于C来说装载两类数据,一类是代码,另一类是静态数据。

静态数据又分全局变量和静态局部变量。

然后指令指针IP指向main函数入口点的对应位置。然后开始执行。

可以发现静态数据是先于main函数的执行而存在,这也是静态数据的一个巨大的作用,一般而言函数都是在main函数执行之后才能执行;

但是一个全局对象的构造函数是先于main函数的执行而执行的。

IP指向main函数入口之后,程序开始执行,也就是IP开始自动往下走,

IP走的方式有两种(有硬件决定),一种是顺序执行(沿着指令的顺序自动往下走,CPU的功能,不需要人为干预),

另一种是跳转(由程序员控制,例如循环(向上跳)和条件判断执行(向下跳))。

2 静态先于mian函数执行

具体例子:

#include <iostream>  
using namespace std;  
  
  
class CTest  
{  
public:  
    CTest()  
    {  
        cout << "构造函数..." << endl;  
    }  
    ~CTest()  
    {  
        cout << "析构函数..." << endl;  
    }  
};  
  
int main()  
{  
    CTest t;
    cout << "main函数..." << endl;
    return 0;  
}  

执行结果:

[root@bj-sg-dev testC]# g++ testStatic.cpp -o testStatic
[root@bj-sg-dev testC]# ./testStatic
构造函数...
main函数...
析构函数...

程序实现方式,启动代码会在启动main()之前完成所有的初始化工作,

这其中当然包括了全局对象的初始化。

这个所谓的启动代码就是Runtime函数库的Startup代码。

在程序执行时,系统会先调用Startup,完成函数库初始化、进程信息设立、I/O stream产生, 以及对static对象的初始化等动作。

然后Startup调用main()函数,把控制权交给main()函数。

main()函数执行完毕,控制权交回给Startup,进行反初始化动作。

引用与参考:

3 静态数据在被加载到内存

静态数据在被加载到内存中,而不是在磁盘上。

int a[N];
int main(){}
build

如上程序,将N设置为10000,而1,比较两个程序的可执行文件exe的大小,发现两个文件一样大。

也就是说这个静态数据,并不在硬盘上,而是操作系统加载时,开辟到内存的。

4 调用

IP在main函数指令执行中遇到调用函数,也就是跳转指令,

跳转到另一个函数的指令位置,然后开始执行,执行完该函数的指令之后,IP又跳回到原来main函数指令的位置。CPU中有一大堆寄存器用来计算。

当调用函数IP还没跳转时,CPU中的寄存器存储的是现在正在计算的那些数据,也就是跳转前的状态。

但是IP跳转之后,CPU开始做新的是事情,也就是CPU中的寄存器开始处理新的计算,而跳转前正在处理数据寄存器的值就会丢失。当该函数调用完之后,IP返回原来的位置,CPU的寄存器也应该会返回到跳转之前的状态,继续干原来的事情。

但是调用函数的时候,CPU的寄存器已经处于调用函数时的状态了。该怎么解决这个问题?

将寄存器的数据进行压栈和出栈,即保护现场和恢复现场。

内存的栈区,其实就是纯粹的数据。返回值为基本类型的直接走寄存器,而返回值为自定义类型的往栈里走

调用一个函数栈里面存放的东西依次是(由编译器决定):CPU原来的状态(现场保护),返回值,形参,然后是一些局部变量的数据。

例如在调用函数时,执行char a = 'a';时,实际就是将’a’这个值进行压栈,将这个数据放到栈的内存里面来进行使用。

压栈出栈其实就是一个栈指针的来回走动。

调用函数传参,其实就是向栈里面的形参的那块内存直接写一个值,

5 局部变量与静态变量

C语言调用函数时将局部变量压栈出栈,那么如果将所有的局部变量都定义为静态可以吗?

不难发现静态全局的访问速度更快,而压栈还需要不断的进栈出栈。

栈是动态使用内存,静态变量是静态使用内存,也就是编译器已经算好它的大小及数量等。

如果将局部变量定义为静态,所有的情况都可以解决,但是有一个问题解决不了,就是自己调用自己,或者说递归。

说明C语言在设计时,递归在整个体系结构中占了很重要的地位。

6 指针

指针是地址吗?如果指针是地址为什么还要起这么个名字,直接叫地址不就好了。

在计算机里指针主要分为两大类,一个是指令指针,也就是程序指针,另一个就是数据指针,数据指针往往用寄存器来代替。

指针就是一个指向数据的地址吗?在计算机中每个字节就是一个地址,

但是一个数据不一定只占有一个地址。

指针指向的是一个数据的首地址,且指针是有类型的。当拿到一个指针的时候,也就是拿到了一个数据的首地址,拿到首地址后应该向下数几个字节来拼成这个数据,就由指针的类型确定,拼成这个数据之后,编译器如何解析这个数据,例如4个字节,该解析为int还是float,这个也由指针的类型来确定。  

指针的三个优点。

第一,以小搏大。

即就地解决,山不过来,我过去的思想。用于函数传参时传址。 传参时不把所有数据都带过去,复制一遍,而是将所有数据的内存地址传过去。

第二,切换。

指令的切换,函数指针,也就是指令指针,例如动态链接库,用指针在动态链接库里来回切换函数, 调用里面的各种函数。数据的切换,数组不是一个类型,只是一个组数据的打包。数据的切换例如数组中的行指针列指针的应用。

第三,挂接,

最常用的挂接就是链表

用指针把一组数据连接起来。

7 delete point

delete point 是将指针与所指地址取消关联,类似于撕户口本,指针依然存在。

8 多思考,提高鉴赏水平。

鉴赏力:例如拿一本书翻几页,知道它要实现个什么东西,然后合上书,自己去思考, 去设计。自己设计完之后,再打开书和别人的去对比,这样就能发现大师设计的想法是多么优秀。 可以看到自己跟大师的差距。学东西最怕的就是学了十几二十年看不到自己和别人差在哪里。 一个人有没有水平就体现在鉴赏力上面,能不能看出它好在哪里。