前言

指针和数组还有很多知识点需要学习


正文

sizeof(数组)

大部分情况下,我们使用sizeof(),编译器都是需要一个运行过程的。
而我们反汇编sizeof求数组大小的时候会发现不一样的点:

能看到反汇编后

1
2
3
4
5
6
int main(){
int ch[5];
int size = sizeof(ch);

return 0;
}

头两句只有一句反汇编代码,首先也是因为数组没有初始化。
其次就是他调用数组大小是20,编译器仿佛知道这个数组的大小了,14h也就是十六进制的写法,换算成十进制也就是20,数组有五个成员*4正好就是20。

也就是之前说的,数组的底层实现是由指针实现的,数组本身是抽象的结构逻辑
所以编译器会提前预览出数组的大小,以便后续使用。


引用

引用是一种阉割的指针版本。
虽然取址引用变量得到的是原值的内存地址,但是引用的变量本身也占用内存。

1
2
3
4
5
6
7
8
9
int a = 5;
a = 6;
int &b = a;
int &c = a;
int *d = &a;
b = 6;
c = 6;
*d = 6;
a = (int)&a;

反汇编后看到:

  1. a的地址就是ebp-Ch
  2. b的地址是ebp-18
    在代码里我们看到&b=a之前,先将a的地址赋予eax,然后再将eax赋予b的地址上
    这就说明了引用变量确实占用了内存。
  3. c的地址就是ebp-24
  4. d的地址就是ebp-30
  5. b=6的时候,先将b的地址的值赋给eax,引用b的地址在上述操作中为a的地址
  6. c=6和*d=6 发现步骤相同,区别就在于各自的内存地址不一样。不过也印证引用和指针的关联性。

堆和栈

空间分配:

  1. 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  2. 堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收,分配方式倒是类似于链表。

百度而来的浅显答案。

1
2
3
4
5
6
7
8
9
int a;
int b;
int c;
int *d = new int[5];

std::cout << &a << std::endl;
std::cout << &b << std::endl;
std::cout << &c << std::endl;
std::cout << d << std::endl;

得到结果:
可以看到abc三个变量的地址离得很近,但是d却很远,d现在打印的地址是new出来的,我们捎上变量d本身的地址。

1
2
3
4
5
std::cout << &a << std::endl;
std::cout << &b << std::endl;
std::cout << &c << std::endl;
std::cout << &d << std::endl;
std::cout << d << std::endl;

能看到d变量本身占用的内存地址里abc都是比较近的,因为他们都是挨着声明的,内存地址由计算机分配而来,也就是说这些变量的内存地址存放在栈区。
而new出来的那块内存则符合定义中由人为分配的,处于堆区。

栈区是程序在编译时就确定了大小的一段内存区域,主要用于存放临时变量,其效率也会高于堆,但是因为事先就确定了大小,导致了容量有限的问题,当然可以在编译前指定栈的大小,不过涉及到的东西有点多

另外就是栈比较有意思的是,他的结构是自上向下的结构,遵循先入后出顺序。

随便画张草图理解一下。
ebp通过地址减去得到变量的地址。当变量的声明周期全部结束了的时候,ebp的地址就会和esp一样了


结语

主要是浅看指针和数组和引用的关系,然后就是顺便理解堆栈的概念,因为反汇编多次出现了,总要先looklook。