diff --git a/.vscode/settings.json b/.vscode/settings.json index 02f7ece..756bfa8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,11 @@ { "cSpell.words": [ "alloca", + "leaq", "movslq", "movsxd", "pcount", - "startproc" + "startproc", + "stdint" ] } \ No newline at end of file diff --git a/CSAPP/L08 Data.md b/CSAPP/L08 Data.md index d922d54..6293422 100644 --- a/CSAPP/L08 Data.md +++ b/CSAPP/L08 Data.md @@ -4,9 +4,15 @@ ## 目录 -- Arrays 数组 -- Structures 结构体 -- Floating Point 浮点数 +- [Arrays 数组](#arrays-数组) + - [Array Access in C C语言中的数组访问](#array-access-in-c-c语言中的数组访问) + - [Multidimensional Arrays 多维数组](#multidimensional-arrays-多维数组) +- [Structures 结构体](#structures-结构体) + - [Explanation 解释](#explanation-解释) + - [Memory Alignment 内存对齐](#memory-alignment-内存对齐) +- [Floating Point 浮点数](#floating-point-浮点数) + - [Floating Point Processor 浮点处理器](#floating-point-processor-浮点处理器) + - [Usage in functions 函数中的使用](#usage-in-functions-函数中的使用) ## Arrays 数组 @@ -54,3 +60,220 @@ get_num: # 这是Debug配置下的编译结果,因此略显冗长 ### Multidimensional Arrays 多维数组 +#### C语言的指针与数组: + +```C + int A1[3]; //是一个整数数组,大小为3 + int *A2[3]; //是一个指针数组,大小为3,每个元素都是一个指向整数的指针 + int (*A3)[3]; //是一个指针,指向一个大小为3的整数数组的第一个元素 +``` + +概念如图所示: + +![指针与数组概念辨析 内存图示](/CSAPP/images/L08%20Array%20&%20Pointer%201.png) +![指针与数组概念辨析 图例](/CSAPP/images/L08%20Array%20&%20Pointer%202.png) + +#### 内存中的二维数组 + +二维数组在内存中是以行优先的方式存储的,即先存储第一行的所有元素,再存储第二行的所有元素,以此类推。 + +```C + int pgh[4][5] = + { + {1, 5, 2, 0, 6 }, + {1, 5, 2, 1, 3 }, + {1, 5, 2, 1, 7 }, + {1, 5, 2, 2, 1 } + }; +``` + +这个数组在内存中存储的顺序如图: +![二维数组在内存中的存储顺序](/CSAPP/images/L08%20Multi%20Demonsion%20Array%20Memory.png) + +也可以以这样的方式理解那个二维数组: + +```C +/* 定义一个类型别名 five_int_arr,表示一个包含5个整数的数组 */ +typedef int five_int_arr[5]; + +five_int_arr pgh[4] = +{ + {1, 5, 2, 0, 6 }, + {1, 5, 2, 1, 3 }, + {1, 5, 2, 1, 7 }, + {1, 5, 2, 2, 1 } +}; +``` + +从图里我们可以看到,对二维数组来说,假设一个二维数组是```T array[R][C]```,其中T是元素类型,R是行数,C是列数,那么: + +- ```array[i]```是一个类型为```T[C]```的一维数组,包含C个元素 + - 起始地址为 ```array + i * C * sizeof(T)``` +- ```array[i][j]```是一个类型为T的元素 + - 地址为 ```array + (i * C + j) * sizeof(T)``` + +#### 访问二维数组的方式 + +你可能想到了: 没错,```lea```机器指令可以被用到。 + +```C +#define PCOUNT 4 +typedef int int_arr[5]; + + int get_num_from_2d_arr(int_arr arr[PCOUNT], int row, int col) { + return arr[row][col]; + } +``` + +```asm +get_num_from_2d_arr: # assume: arr -> %rdi, row -> %esi, col -> %edx + movslq %esi, %rsi # 将row转换为64位 + leaq (%rsi,%rsi,4), %rax # %rax = %rsi + %rsi * 4 + leaq (%rdi,%rax,4), %rax # %rax = %rdi + %rax * 4,得到arr[row]的地址 + movslq %edx, %rdx # 将col转换为64位 + movl (%rax,%rdx,4), %eax # 从%rax + %rdx * 4加载元素到eax + ret +``` + +#### 假装是二维数组的指针数组 + +还有一种情况,一维数组存储指向一维数组的指针。 + +```C + int_arr mit = {0, 2, 1, 3, 9}; + int_arr cmu = {1, 5, 2, 1, 3}; + int_arr ucb = {9, 4, 7, 2, 0}; + + int_arr *arr[3] = {&mit, &cmu, &ucb}; +``` + +它也可以用get_num_from_2d_arr C语言函数来获取对应位置的数据,但**在内存中的情况完全不同**。它的内存布局长这样: +![存有指向一维数组的指针的一维数组的内存布局](/CSAPP/images/L08%20Array%20Pointer%20Array%20Memory.png) + +## Structures 结构体 + +### Explanation 解释 + +结构体的内存就是简单的把东西接在一起,考虑内存对齐即可。 + +```C +#include +struct structExample +{ + uint32_t a[3]; + uint16_t shortInt; + uint64_t next; +}; +``` + +![结构体的内存布局](/CSAPP/images/L08%20Struct%20Example%20Memory%20Layout.png) + +因此,对结构体的访问也很简单。编译器直接“按图索骥”,根据定义找到对应的偏移量就行了。 + +```C +uint16_t get_short_int(struct structExample *e) { + return e->shortInt; +} +``` + +```asm +get_short_int: + movzwl 12(%rdi), %eax # 从结构体e的地址(%rdi) + 12字节处加载shortInt到%eax + ret +``` + +### Memory Alignment 内存对齐 + +#### 解释 + +出于硬件原因[^Memory-Alignment-Hardware-Reason],结构体中的成员在内存中的位置需要按照特定的边界进行对齐。这意味着编译器可能会在成员之间插入填充字节,以确保每个成员都位于正确的内存地址上。 + +[^Memory-Alignment-Hardware-Reason]: 内存对齐是为了提高访问效率。现在的机器一次从内存中读取大约64字节。如果数据没有对齐,可能需操作系统或者硬件的额外处理,导致效率降低。 + +- 编译器会找到最大的成员的大小,并将结构体的总大小调整为该大小的倍数。内存对齐也会按照该大小进行。 + +例如,在上图的例子中,你可以看到uint64_t next成员是最大的,占用8字节,因此结构体的总大小被调整为24字节(8的倍数)。编译器在shortInt成员之后插入了2字节的填充,以确保next成员从一个8字节边界开始。 + +#### 优化布局 + +由于编译器有时不会自动优化结构体内存布局,因此在定义结构体时,**合理安排成员的顺序**可以减少填充字节的数量,从而节省内存空间。例如如下结构体: + +```C +struct badStruct +{ + uint8_t a; + uint64_t b; + uint16_t c; + uint32_t d; +}; +``` + +![badStruct的内存布局](/CSAPP/images/L08%20Bad%20Struct%20Memory%20Layout.png) +它足足空出来了8个字节的空间没有使用!这使得它的总大小达到了24字节。 + +如果我们稍稍改动一下排布顺序...... + +```C +struct goodStruct +{ + uint64_t b; + uint32_t d; + uint16_t c; + uint8_t a; +}; +``` + +![goodStruct的内存布局](/CSAPP/images/L08%20Good%20Struct%20Memory%20Layout.png) +这样子,我们只需要1个字节的填充。结构体的总大小也因此减少到了16字节。 + +通常来说,将结构体成员按照**从大到小**的顺序排列,可以最大程度地减少填充字节的数量。 + +## Floating Point 浮点数 + +### Floating Point Processor 浮点处理器 + +浮点数的运算使用一组完全不同的寄存器与指令集。在现代的CPU上,大多数使用AVX指令集处理浮点数。它使用16个256位的寄存器,分别为```YMM0```到```YMM15```,可以同时处理多个浮点数。 + +- 如果只是简单的浮点数运算,CPU可能仅使用SSE指令集和128位的```XMM```寄存器。要真正利用 AVX 的高带宽,需要编写一次性将四个双精度浮点数相加的代码。 +- ```XMM``` 寄存器实际上只是 ```YMM``` 寄存器的低半部分 。如果你写入 ```XMM0``` ,从技术上讲,你实际上是在操作 ```YMM0``` 的前 128 位。 + +```C +float add_floats(float a, float b) { + return a + b; +} +``` + +```asm +add_doubles: + addsd %xmm1, %xmm0 + ret +``` + +可以在[英特尔内部组件指南](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html)找到所有的AVX2指令和它们的功能描述。对于浮点数运算,常见的指令包括: + +- ```addss```:对两个寄存器内的第一个单精度浮点数进行加法运算 +- ```addps```:对两个寄存器内的所有单精度浮点数进行加法运算 + +它们的图解如下图所示: +![addss和addps指令的图解](/CSAPP/images/L08%20ADDSS%20&%20ADDPS%20Diagram%20.png) +*这里使用了SSE的128位寄存器来演示addss和addps指令的功能。对于AVX的256位寄存器,addss和addps指令的功能是一样的,只不过它们可以同时处理更多的浮点数。* + +- ```addsd```:对两个寄存器内的第一个双精度浮点数进行加法运算 +- ```addpd```:对两个寄存器内的所有双精度浮点数进行加法运算 + +![addsd指令的图解](/CSAPP/images/L08%20ADDSD%20Diagram.png) +有了前面```addss```和```addps```的图解,```addsd```就不难理解了。它们的区别在于处理的数据类型不同,```addss```和```addps```处理单精度浮点数,而```addsd```和```addpd```处理双精度浮点数。 + +与此类似,```addpd```指令可以同时处理两个寄存器内的所有双精度浮点数进行加法运算。对于AVX的256位寄存器,```addpd```可以同时处理四个双精度浮点数进行加法运算。 + +理解了这里之后,AVX指令集的拓展也不难理解。它的进步之一是引入了```vaddss```这种指令。它允许第三个寄存器作为目标,将运算结果存储在那个寄存器,而不是覆盖其中一个源寄存器,例如```VADDPS YMM2, YMM1, YMM0```。 + +### Usage in functions 函数中的使用 + +在函数中使用浮点数时,根据x86-64 **Linux** System V ABI约定: + +- 通过```YMM0```到```YMM7```寄存器传递前8个浮点数参数[^Windows-x64-ABI] +- 返回值通过```YMM0```寄存器返回 +- 所有```YMM```寄存器的高128位都是易失性的,必须由**调用者**负责保存和恢复。[^Windows-x64-ABI] + +[^Windows-x64-ABI]: Windows x64 ABI与Linux System V ABI在寄存器使用约定上有一些不同。Windows x64 ABI仅使用```XMM0```到```XMM3```寄存器传递浮点数参数;且要求被调用者保存```XMM6```到```XMM15```寄存器,而```XMM0```到```XMM5```与Linux相同。所有```YMM```寄存器也与Linux相同,由调用者负责保存和恢复。 diff --git a/CSAPP/images/L08 ADDSD Diagram.png b/CSAPP/images/L08 ADDSD Diagram.png new file mode 100644 index 0000000..1b02e47 Binary files /dev/null and b/CSAPP/images/L08 ADDSD Diagram.png differ diff --git a/CSAPP/images/L08 ADDSS & ADDPS Diagram .png b/CSAPP/images/L08 ADDSS & ADDPS Diagram .png new file mode 100644 index 0000000..087e1a7 Binary files /dev/null and b/CSAPP/images/L08 ADDSS & ADDPS Diagram .png differ diff --git a/CSAPP/images/L08 Array & Pointer 1.png b/CSAPP/images/L08 Array & Pointer 1.png new file mode 100644 index 0000000..4c457bc Binary files /dev/null and b/CSAPP/images/L08 Array & Pointer 1.png differ diff --git a/CSAPP/images/L08 Array & Pointer 2.png b/CSAPP/images/L08 Array & Pointer 2.png new file mode 100644 index 0000000..104b0e5 Binary files /dev/null and b/CSAPP/images/L08 Array & Pointer 2.png differ diff --git a/CSAPP/images/L08 Array Pointer Array Memory.png b/CSAPP/images/L08 Array Pointer Array Memory.png new file mode 100644 index 0000000..fa774bd Binary files /dev/null and b/CSAPP/images/L08 Array Pointer Array Memory.png differ diff --git a/CSAPP/images/L08 Bad Struct Memory Layout.png b/CSAPP/images/L08 Bad Struct Memory Layout.png new file mode 100644 index 0000000..a45f038 Binary files /dev/null and b/CSAPP/images/L08 Bad Struct Memory Layout.png differ diff --git a/CSAPP/images/L08 Good Struct Memory Layout.png b/CSAPP/images/L08 Good Struct Memory Layout.png new file mode 100644 index 0000000..f0f564a Binary files /dev/null and b/CSAPP/images/L08 Good Struct Memory Layout.png differ diff --git a/CSAPP/images/L08 Multi Demonsion Array Memory.png b/CSAPP/images/L08 Multi Demonsion Array Memory.png new file mode 100644 index 0000000..8ece74c Binary files /dev/null and b/CSAPP/images/L08 Multi Demonsion Array Memory.png differ diff --git a/CSAPP/images/L08 Struct Example Memory Layout.png b/CSAPP/images/L08 Struct Example Memory Layout.png new file mode 100644 index 0000000..641f0e4 Binary files /dev/null and b/CSAPP/images/L08 Struct Example Memory Layout.png differ