docs(struct): edit 属性的内存占用

This commit is contained in:
ruanyf
2022-03-31 11:16:39 +08:00
parent baf63947cc
commit 1e70e6f2d6

View File

@@ -147,16 +147,22 @@ numbers[0].denominator = 7;
上面示例声明了一个有1000个成员的数组`numbers`,每个成员都是自定义类型`fraction`的实例。
struct 结构占用的存储空间,不是各个属性存储空间的总和,而是最大内存占用元素的倍数占位不足的元素会进行对齐填充。比如以64位机器为例
struct 结构占用的存储空间,不是各个属性存储空间的总和,而是最大内存占用属性的存储空间的倍数,其他属性会添加空位与之对齐。这样可以提高读写效率
```c
// 占24个字节sizeof(struct foo) = 24;
struct foo {
int a;
char *b;
char* b;
char c;
};
printf("%d\n", sizeof(struct foo)); // 24
```
在64位机器中`int a`占4个字节指针`char *b`类型占8个字节`char c`占1个字节实际有效存储空间13字节。但编译器会进行内存对齐填充即向占用内存空间最大的指针`char *b`对齐,具体做法是空位填充。
上面示例中,`struct foo`有三个属性在64位计算机上占用的存储空间分别是`int a`占4个字节指针`char* b`占8个字节`char c`占1个字节。它们加起来一共是13个字节4 + 8 + 1。但是实际上`struct foo`会占用24个字节原因是它最大的内存占用属性是`char* b`的8个字节导致其他属性的存储空间也是8个字节这样才可以对齐导致整个`struct foo`就是24个字节8 * 3
多出来的存储空间,都采用空位填充,所以上面的`struct
foo`真实的结构其实是下面这样。
```c
struct foo {
int a; // 4
@@ -165,36 +171,24 @@ struct foo {
char c; // 1
char pad2[7]; // 填充7字节
};
printf("%d\n", sizeof(struct foo)); // 24
```
为什么浪费这么多内存空间进行内存对齐这是为了减少CPU的访存次数毕竟比起高速运转的CPU和寄存器访存是最耗时间的操作。一般情况让变量的存储地址是它占用字节的倍数例如让`long`型变量的地址是8的倍数(例如: 0x0008)`int`型变量的地址是4的倍数`short`型是2的倍数`char`占一个字节,但一般都会在`char`型变量后进行空字节填充即使不是在结构体中。这样处理后CPU就可以按照字或者半字寻址一次访存就定位到变量的起始地址。
由于结构体的这一特性,一般在定义结构体的时候,采用内存占用递减进行元素排序,这样可以比较有效的优化内存占用,原因如下
为什么浪费这么多空间进行内存对齐呢?这是为了加快读写速度,把内存占用划分成等长的区块,就可以快速在 Struct 结构体中定位到每个属性的起始地址
由于这个特性,在有必要的情况下,定义 Struct 结构体时,可以采用存储空间递减的顺序,定义每个属性,这样就能节省一些空间。
```c
// 占24个字节sizeof(struct foo) = 24;
struct foo {
int a;
char *b;
char c;
};
// 内存占用递减排序元素占16个字节sizeof(struct foo) = 16;
struct foo {
char *b;
int a;
char c;
};
```
上边代码,改变了结构体中`char *b``int a`的顺序内存占用就从24字节下降到16字节因为此时编译器只需要进行尾填充即可。
```c
// 占16个字节sizeof(struct foo) = 24;
struct foo {
char *b; // 8
int a; // 4
char c; // 1
char pad[3]; // 填充3字节
char c;
int a;
char* b;
};
printf("%d\n", sizeof(struct foo)); // 16
```
上面示例中,占用空间最小的`char c`排在第一位,其次是`int a`,占用空间最大的`char* b`排在最后。整个`strct foo`的内存占用就从24字节下降到16字节。
## struct 的复制
struct 变量可以使用赋值运算符(`=`),复制给另一个变量,这时会生成一个全新的副本。系统会分配一块新的内存空间,大小与原来的变量相同,把每个属性都复制过去,即原样生成了一份数据。这一点跟数组的复制不一样,务必小心。