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