mirror of
https://github.com/Aexiar/c.git
synced 2024-10-22 14:05:45 +02:00
2024年10月11日 12:16
This commit is contained in:
parent
d4a8fbaa11
commit
48a17acf0a
4
docs/notes/02_c-leap/07_xdx/assets/14.svg
Normal file
4
docs/notes/02_c-leap/07_xdx/assets/14.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 77 KiB |
4
docs/notes/02_c-leap/07_xdx/assets/15.svg
Normal file
4
docs/notes/02_c-leap/07_xdx/assets/15.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 148 KiB |
4
docs/notes/02_c-leap/07_xdx/assets/16.svg
Normal file
4
docs/notes/02_c-leap/07_xdx/assets/16.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 148 KiB |
4
docs/notes/02_c-leap/07_xdx/assets/17.svg
Normal file
4
docs/notes/02_c-leap/07_xdx/assets/17.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 123 KiB |
@ -1313,8 +1313,64 @@ int main() {
|
||||
> [!NOTE]
|
||||
>
|
||||
> * ① 对于一个 int 类型的数据而言,其在内存中的长度是 4 个字节。
|
||||
> * ② 如果其存储时的内存地址的编号是 8 ,非常好办,直接对编号为 8 的内存进行寻址一次就可以了。
|
||||
> * ③ 但是,如果其存储时的内存地址的编号是 10,就比较麻烦,CPU 首先需要先对编号为 8 的内存进行寻址,读取 4 个字节,得到该数据的前半部分,然后再对编号为 12 的内存进行寻址,读取 4 个字节,得到该数据的后半部分,再将这两部分数据拼接起来,才能取得数据的值。
|
||||
>
|
||||
> ![](./assets/14.svg)
|
||||
>
|
||||
> * ② 如果其存储时的内存地址的编号是 4 ,非常好办,直接对编号为 4 的内存进行寻址一次就可以了。
|
||||
>
|
||||
> ![](./assets/15.svg)
|
||||
>
|
||||
> * ③ 如果其存储时的内存地址的编号是 6,就比较麻烦,CPU 首先需要先对编号为 4 的内存进行寻址,读取 4 个字节,得到该数据的前半部分,然后再对编号为 8 的内存进行寻址,读取 4 个字节,得到该数据的后半部分,再将这两部分数据拼接起来,才能取得数据的值。
|
||||
>
|
||||
> ![](./assets/16.svg)
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> * ① 将一个数据尽量放到一个步长之内,避免跨步长存储和读取,这称为`内存对齐`。
|
||||
> * ② 在 `32` 位编译模式下,默认以 `4` 字节对齐。在 `64` 位编译模式下,默认以 `8` 字节对齐。
|
||||
|
||||
* 为了满足对齐要求,编译器有时会在数据结构中插入一些“填充”字节,这就会产生一定的内存浪费。例如:假设一个结构体包含一个`char`和一个`int`字段,编译器可能会插入 3 个字节的填充以确保`int`字段对齐到 4 字节的边界。这种填充虽然会浪费少量内存,但可以显著提升数据访问效率,如下所示:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
struct {
|
||||
int a;
|
||||
char b;
|
||||
int c;
|
||||
} t = {10, 'C', 20};
|
||||
|
||||
int main() {
|
||||
|
||||
// 禁用 stdout 缓冲区
|
||||
setbuf(stdout, nullptr);
|
||||
|
||||
printf("sizeof(t): %zu\n", sizeof(t)); // sizeof(t): 12
|
||||
printf("&a: %p\n", &t.a); // &a: 0x559d28db8010
|
||||
printf("&b: %p\n", &t.b); // &b: 0x559d28db8014
|
||||
printf("&c: %p\n", &t.c); // &c: 0x559d28db8018
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
* 其内存结构,如下所示:
|
||||
|
||||
![](./assets/17.svg)
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> * ① 不管是`结构体变量`,还是`普通变量`都存在内存对齐。
|
||||
> * ② 内存对齐的规则:只能存放在自己类型整数倍的内存地址上(`内存地址`和`占用字节`是可以整除)。
|
||||
> * 1 字节的数据,如:`char`类型,通常可以存放在任何地址。
|
||||
> * 2 字节的数据,如:`short`类型,通常存放在偶数地址。
|
||||
> * 4 字节的数据,如:`int`类型,通常存放在能被 4 整除的地址。
|
||||
> * 8 字节的数据,如:`double`类型,通常存放在能被 8 整除的地址。
|
||||
> * ③ `结构体变量`的内存对齐:`结构体变量`的内存对齐除了遵循上述的内存规则之外,还会插入“填充”字节。`结构体变量`的总大小是最大类型的整数倍。
|
||||
> * ④ 内存对齐的时候可能会出现“填充”字节,但是并不会改变原来数据的大小,即:char 类型的数据即使“填充”字节之后,本身还是 1 个字节。
|
||||
> * ⑤ `心得`:我们会将小的数据类型,写在最上面;大的数据类型,写在最下面(节省内存空间)。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user