CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)

1.对数组的理解
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

从上述简单的数组赋值,及其底层代码可以看出,数组赋值过程其实就是对一段连续的内存赋值。int是四个字节,每个元素的地址间隔都是四个字节。char是一个字节,每个元素的地址间隔都是一个字节。从上述我们就知道数组在内存中的样子。看到连续的内存就想到了指针。只要知道数组a的首地址和数组长度,就可以准确定位数组在内存中的空间位置。如下图所示。
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

2.数组和指针之间的关联
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

void fun1(int a[2]) { a[0] = 1; a[1] = 2; } void fun2(int* a) { *a = 1; *(a + 1) = 2; }

fun1(int*): push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov rax, QWORD PTR [rbp-8] mov DWORD PTR [rax], 1 mov rax, QWORD PTR [rbp-8] add rax, 4 mov DWORD PTR [rax], 2 nop pop rbp ret fun2(int*): push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov rax, QWORD PTR [rbp-8] mov DWORD PTR [rax], 1 mov rax, QWORD PTR [rbp-8] add rax, 4 mov DWORD PTR [rax], 2 nop pop rbp ret

1.上述第一函数是一个简单的用数组变量做参数的函数。第二个函数是一个简单的用指针变量做参数的函数。从汇编指令可以看出,CPU在处理时,一模一样。
2.所以,不管你的数组有多大,你的编译器都不会像普通变量一样给你在堆栈中构建一个临时的数组变量,而是传递一个数组的内存首地址,并存放在一个临时的指针变量里面。
3.数组读写等于指针的*操作。
3.数组的调用
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

void fun1(int a[2]) { a[0] = 1; a[1] = 2; } void fun2(int* a) { *a = 1; *(a + 1) = 2; } int call() { int a[2] = {0,9}; fun1(a); fun2(a); fun2(&a[0]); }

fun1(int*): push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov rax, QWORD PTR [rbp-8] mov DWORD PTR [rax], 1 mov rax, QWORD PTR [rbp-8] add rax, 4 mov DWORD PTR [rax], 2 nop pop rbp ret fun2(int*): push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov rax, QWORD PTR [rbp-8] mov DWORD PTR [rax], 1 mov rax, QWORD PTR [rbp-8] add rax, 4 mov DWORD PTR [rax], 2 nop pop rbp ret call(): push rbp mov rbp, rsp sub rsp, 16 mov DWORD PTR [rbp-8], 0 mov DWORD PTR [rbp-4], 9 lea rax, [rbp-8] mov rdi, rax call fun1(int*) lea rax, [rbp-8] mov rdi, rax call fun2(int*) lea rax, [rbp-8] mov rdi, rax call fun2(int*) nop leave ret

1.上述三种调用的汇编指令完全相同,都是在传递数组的内存首地址。而非构建临时数组变量。
2.我们需要习惯的时传递指针的形式。
void func(int* array)
为了防止越界,一般会增加一个数组长度的参数。
void func(int* array, int length)
4.多维数组
在很多人眼里,对于多维数组在内存中的样子如下。
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

但其实多维数组在内存中是一维的,在cpu眼中,他们都是一维数组。如下图。
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

下面我们分别写三个函数,分别是一维,二维,三维。看看他们的汇编指令有什么区别。
1.一维数组CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

2.二维数组
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

3.三维数组
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

从上述对一维、二维、三维数组赋值可以看出,他们的汇编指令完全相同。他们都是一段连续的一维内存。
void fun1() { int a[8]; a[0] = 1; a[1] = 2; a[2] = 3; a[3] = 4; a[4] = 5; a[5] = 6; a[6] = 7; a[7] = 8; }void fun2() { int a[2][4]; a[0][0] = 1; a[0][1] = 2; a[0][2] = 3; a[0][3] = 4; a[1][0] = 5; a[1][1] = 6; a[1][2] = 7; a[1][3] = 8; }void fun3() { int a[2][2][2]; a[0][0][0] = 1; a[0][0][1] = 2; a[0][1][0] = 3; a[0][1][1] = 4; a[1][0][0] = 5; a[1][0][1] = 6; a[1][1][0] = 7; a[1][1][1] = 8; }

fun1(): push rbp mov rbp, rsp mov DWORD PTR [rbp-32], 1 mov DWORD PTR [rbp-28], 2 mov DWORD PTR [rbp-24], 3 mov DWORD PTR [rbp-20], 4 mov DWORD PTR [rbp-16], 5 mov DWORD PTR [rbp-12], 6 mov DWORD PTR [rbp-8], 7 mov DWORD PTR [rbp-4], 8 nop pop rbp ret fun2(): push rbp mov rbp, rsp mov DWORD PTR [rbp-32], 1 mov DWORD PTR [rbp-28], 2 mov DWORD PTR [rbp-24], 3 mov DWORD PTR [rbp-20], 4 mov DWORD PTR [rbp-16], 5 mov DWORD PTR [rbp-12], 6 mov DWORD PTR [rbp-8], 7 mov DWORD PTR [rbp-4], 8 nop pop rbp ret fun3(): push rbp mov rbp, rsp mov DWORD PTR [rbp-32], 1 mov DWORD PTR [rbp-28], 2 mov DWORD PTR [rbp-24], 3 mov DWORD PTR [rbp-20], 4 mov DWORD PTR [rbp-16], 5 mov DWORD PTR [rbp-12], 6 mov DWORD PTR [rbp-8], 7 mov DWORD PTR [rbp-4], 8 nop pop rbp ret

总结:
1.数组是一段连续的内存,除了定义数组变量会用数组表示,也会用指针表示。并用指针的*操作来读写数组元素。
2.传递数组参数,本质上是在传递指针。所以,在函数内改变数组的值,也会改变函数外数组的值。
函数调用前
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

函数调用后
CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)
文章图片

3.高维数组本质上还是一维数组。只是索引方式不同,应用场景不同而已。
【CPU底层|【CPU底层那些事(数组和指针真的一样吗()】)】往期链接:
1: CPU底层那些事(二进制陷阱).
2: CPU底层那些事(底层视角看i++ VS ++i).
3: CPU底层那些事(左值VS右值).
4: CPU底层那些事(CPU如何读写变量).
5: CPU底层的那些事(main函数).

    推荐阅读