再读C++的设计艺术(4)
1 先声明后使用原则
声明(声明变量和函数)是给编译器看的。
定义变量时才会开辟内存,声明不会开辟,声明是个编译器看的,如果声明了一个变量, 且后面没有使用,则编译器会将其优化,视为不存在,不会开辟内存。
写一个函数的顺序是先声明,再定义,最后调用。
但是声明和定义可以合起来写。例如下面的写法:
void Swap(int, int);
void main() {
Swap(a ,b);
}
void Swap(int x, int y) {
int temp = x;
...
}
首先声明函数Swap是为了给编译器看,知道了其返回值类型和参数类型几个数,只是为了计算其在 栈中占用空间,方便栈指针跳转等(不得而知),所以不用写参数名字,有了其类型就够了。
而定义的时候,是必须要知道参数的名字的,第一因为函数中要用到这个参数。
但是问题来了,如果不写参数名字,调用函数时形参在从左到右一个一个的进栈时,由此每个参数在栈中的内存地址我都知道了,函数中的使用不就可以直接访问那个地址了吗?这也就导致了第二个原因,函数参数进栈的顺序是不一定的,所以必须为其加上名字,后面才能准确拿到参数。
2 函数参数进栈顺序
首先,参数入栈顺序为:从右向左
原因:是为了在使用C/C++的“函数参数长度可变”这个特性时更方便。
如果是从左向右压栈,第一个参数将被放在栈底,最后一个可变参数放在栈顶,由于可变,第一个栈顶的参数取起来特别麻烦。
从右向左,最后一个可变参数放在栈底,第一个放在栈顶,可以快速方便取出第一个参数。
3 ++a 与a++
如下例子:
#include <cstdio>
int main() {
int a = 10;
printf("%d %d %d\n", a++, ++a, a);
return 0;
}
按照从右向左读取,想当然的结果应该是:11 11 10,执行后a = 12。
但是看结果发现结果是:11,12,12
这是为什么呢?
原因在于:
1. 在将参数入栈前,编译器会先把参数的的表达式都处理掉,哪怕这些运算会改变其中某些参数的值,
2. ++a 是直接在变量a地址所指的值,直接加1;
3. a++ 是先取变量a地址所指的值,将该值备份到缓冲区,然后再对a地址所指的值加1**
看如下例子:
#include<stdio.h>
int main() {
int a = 11;
printf("%d %d %d %d %d %d\n", ++a, a++, a++, ++a, a, a++);
return 0;
}
猜想如下:
| a++缓冲区 | a |
|---|---|
| 11 | 12 |
| 11 | 12 |
| 11 | 13 |
| 13 | 14 |
| 14 | 15 |
| 14 | 16 |
16,14,13,16,16,11
实际输出结果为:
16 14 13 16 16 11
猜想正确。
引用与参考:
https://www.cnblogs.com/easonliu/p/4224120.html
https://blog.csdn.net/hnyzyty/article/details/46427219
4 地址重定位
例如在main.c中调用了a.h中的函数,a.h中的函数只声明而不定义,所以在main.obj中,也就是在main.c编译之后的机器码中,IP跳转到该函数的地址是不知道的,因为该函数的.obj还没有链接到一起,所以这些调用函数地方指针都空出来了,当所有obj都拼到一起之后,编译器算出该函数地址的值赋值给原来指针的地方,也就是地址重定位。
5 程序打包
一个.cpp文件编译之后一般来说生成一个.obj文件(目标文件,机器码,可执行,但不是立即可执行)。然后将多个编译生成的.obj文件以及标准库等其他的.obj文件拼在一起后,然后进行地址重定位,将其之间的关系全部联系起来。最后加入一些头等打包成exe。
6 静态链接与动态链接
所谓lib库等,就是将一大堆的.obj文件合起来,然后加入一些引导头之类的。
上述讲的这些链接,都是静态链接,链接之后还是在硬盘中,执行的时候加载到内存中。
例如printf这个函数的代码,在运行之前已经存在于exe,这就是静态链接。
相对的就是动态链接dll。
链接printf的时候,不是讲”stdio.h”这个库连接进去,而是将printf链接进去,以最简化原则, 需要谁连接谁。
静态链接中,例如连续调用多次printf函数,实际上就链接了一次,只产生一份代码,只是传的参数不同。
但是站在操作系统的角度来看,系统中有多个程序在执行,每个程序都调用了printf,也就是每个程序都 链接了一次printf。站在程序的角度看,自己已经很节约了只链接了一次,但是站在操作系统的角度看, 链接了多次,产生了浪费,其实只要搞出一份代码就可以,自己的程序访问别人程序的代码,但是这样就导致了跨进程的问题,十分不安全。而动态链接库就解决了这个问题。动态链接库没有将代码放到某个程序中,而是放到操作系统中,当某个程序使用其的时候,应用loadLibrary这个技术,加载该库。
如果要用printf函数,就从dll中找到该函数的地址。原来是静态的算好printf函数的地址,现在变成了动态获取printf函数的地址。
7 作用域与生存周期。
static局部变量,全局的生存周期,局部的作用域。