指针与内存对齐到底是什么鬼?

什么是指针?先看看什么是内存地址
首先,我们要搞清楚数据结构在计算机里面到底怎么存取?怎么描述它们 。
任何数据结构(struct)以及组成数据结构的基本数据类型,一旦分配了内存空间,那么就会有两个部分来描述这块内存:内存的地址(红色部分,不占用实际空间,相当于门牌号,用于寻址)与内存的值(绿色部分,是实际的信息存储部分,占用内存空间,以byte为单位) 。就像下面这张图:

指针与内存对齐到底是什么鬼?

文章插图
数据在内存中的结构
所以一块内存,或者一个符号(编程语言的符号其实就代表了一块内存,所以它们代表同一个意思)有两个重要的属性:
 
  1. 内存或者符号的地址
  2. 内存或者符号的值
 
这两个属性如同一个事物的两面,不可分割,形影不离 。
有时候,如果对事情的本质进行深挖的话,你可能对一些基本概念有更深刻的理解 。比如,到这里,如果你理解了内存或者编程语言的符号有两个基本的属性:地址与值,那么你就可以理解C/C++中的&与=操作符的含义 。
 
  • &作用在一个符号上的底层含义就是——获取这个符号的两个重要属性之一——符号的地址
  • =作用在一个符号上的底层含义就是——获取这个符号的两个重要属性之一——符号的内存值 。int a=1;含义就是获取符号a的内存值,并将内存值赋值成1 。
 
可以推断出,从CPU的角度,或者编程语言底层来看,没有数据类型的概念,任何数据都是一块块连续的、长短不一的内存存储单元而已,就像上图所画 。那么问题就变成了,怎么描述这块内存呢?
答案是:内存的起始地址+长度 。比如下面这个结构:
struct Test {int a;short b;
对于test这个结构,怎么描述它?
答案是:struct test是——符号a的内存地址+6个字节长度的数据块,如果要读取或者写入test某个部分(a或者b),编译器至少要编译两条指令:1、获取test也就是a符号的地址,2、根据类型定位偏移量就行了 。这就是数据结构的本质了 。
那么对数据结构成员变量的访问就很容易理解了:
 
  • test.a就可以被编译成符号a的地址+向高地址取4个字节的内存块 。
  • test.b就可以看成符号a的地址向高地址偏移4个字节+向高地址取2个字节的内存块 。
 
是不是有点类似数学中的极坐标系的概念 。而实际上系统确实是这么做的 。
站在编译器的角度看看符号与变量
指针在C与C++中很难理解,但是又是重要的构成部分,没有了指针其实就发挥不出语言的光芒了 。因为指针是很自然的事物,它是承接CPU取址与程序可读性的关键概念,理解了它就既能看穿机器的运行,又能写出合理的优雅的代码去描述业务 。
要真正理解指针或者更普遍的意义来说,理解符号,就得将自己想象成编译器去读代码,这样一切都会变得理所当然的容易起来 。
我们看到的程序都是由变量符号组成的,本质上符号代表一块内存,比如上面的结构体就有三个变量符号或者简称符号:test,a,b 。每个符号其实都对应一块型如下图的内存块:
指针与内存对齐到底是什么鬼?

文章插图
内存块的两个维度
再来看看这个代码片段
typedef struct test {int a;short b;} Test;Test t;t.a =1;t.b =2;Test* t_ptr = &t;t_ptr->a = 3;