C++——内存与指针

program memory

我们在程序中使用的内存地址都是虚拟内存地址,会被计算机映射到真实的物理地址。

这样做的好处有

  1. 一个程序可以保持一直用相同的内存地址
  2. 使不同程序使用的地址分隔开,保证安全

一个程序使用的program memory如下

image

program memory中,栈和堆共用一个内存.在图中,内存的地址是向上增长的,从图上可见,stack向下增长,heap向上增长。但这并不说明stack上越新的变量地址越小,heap上越新越大,C标准没有规定这件事。

动态分配内存

当我们普通创建局部变量时,都是使用的stack上的内存,但stack上的内存极小,往往被限制在个位数MB,而heap的内存则是GB数量级,所以我们在创建较大的数组、对象时,可以使用动态内存,也就是使用heap上的内存

C中动态分配内存用的是malloc()等,C++中用的是new

但heap上的内存不会自动回收,一定要主动回收。C中用的是free(),C++用的是delete,delete[]

内存对齐

内存对齐的目的是:对于每一个数据,都能够一次读出来

对齐值的实际含义是,对于对齐值n,CPU每次会从内存中读取n个字节,而不是逐字节读取

程序在给变量分配内存时,如果多个变量可以同时完整地容纳进一个内存块中,那就会被放在同一块内存块里,如果不行,则会按先后顺序尽量多地往这个内存段中放入变量,最后该内存段会有留空的部分,容纳不下的变量会被放在下一个n字节内存块中,这就是内存对齐

内存对齐的规则如下
1.基本类型的对齐值就是其sizeof值;
2.结构体的对齐值是其成员的最大对齐值;
3.可以通过 "#pragma pack(n)" 设置一个最大对齐值,类型的实际对齐值是该类型的对齐值与规定的最大对齐值中的较小值。

struct my_struct {
    char c1;
    char c2
    long long l;
};

当没有规定最大对齐值,sizeof(my_struct)在我的机器上返回16
因为my_struct的对齐值是8,c1和c2都可以装入第一个8字节内存块中,而 \(l\) 放不进去,就会被放入新的一个8字节内存块

当规定了最大对齐值为4,sizeof(my_struct)返回12

小端序

C++采用的是小端序,也就是变量的值存储在内存中,是从低位存起的,这样方便运算。

假如说我们要将0X0A0B0C0D这个数存储在栈上,若我们用一个字节一个字节的方式来观察这个数,那么0D的地址最高,0A最低

如下是一个展示小端序的程序

int number = 0X0A0B0C0D;
char *ptr = (char*)&number;
cout << (int)*ptr << endl;
output: 13

指针

指针的本质也是一个普通的变量,当我们说某个指针ptr指向另一个变量number时,这个ptr变量的值就是number的地址(虚拟地址)。因为指针本身是一个变量,所以指针也有地址,所以存在指针的指针。

指针的大小

一个指针的大小在64位机器上是64位长度,也就是8字节的。但是我们将指针存储的地址打印出来,会发现是48位的。这是因为,指针有64位就代表我们有64位的寻址长度,C++中单位内存是字节,地址一个单位就代表一个字节,通过计算可以得知64位的计算机上的程序可以有18EB的虚拟内存。但是我们不需要这么大的虚拟内存,实现64位长的虚拟地址只会增加系统的复杂度和地址转换的成本,带不来任何好处,所以 Windows 和 Linux 都对虚拟地址进行了限制,仅使用虚拟地址的低48位(6个字节),总的虚拟地址空间大小为 2^48 = 256TB。

指针的工作方式

指针指向一个变量后,指针的值是这个变量首字节的地址(是字节而不是位)。指针是根据自己的类型来解析内存上的内容的。

int number = 0X0A0B0C0D;
int *ptr = &number;
cout << *ptr << endl;
result: 168496141(0X0A0B0C0D)

如上示代码,在解引用这个指针时,指针根据自己的类型int*,按照int的大小——4个字节(64位机器上),在内存上从它记录的地址开始找了4个字节作为它解引用的值

再看下方的代码

int number = 0X0A0B0C0D;
char *ptr = (char*)&number;
cout << (int)*ptr;
result: 13(0D)

这个指针按照char*的方式,在内存上找了一个字节作为它的值

指针上的运算

我们可以在指针上做加减运算,代表的含义是在内存上移动,+/- 1 就代表在内存上移动一个对象的大小,这个大小取决于指针的类型

char *ptr;
ptr += 1;

这时候ptr地址增加一个字节

int *ptr;
ptr += 1;

这时候ptr地址增加四个字节

指针和数组的关系

指针和数组名经常是可以互换的关系

char *ptr;
char array[n];

数组名与一个const pointer非常相似,我们很多时候可以把他看作一个const pointer,我们不能改变这个指针的值,这个指针的值始终是一块连续内存的首地址,也就是数组的首地址

char array[5] = "abc";
printf("%p\n", &array[0]);
printf("%p\n", array);
result: 
0x7ffe98597f13
0x7ffe98597f13

因为指针可以通过加减运算来指向连续内存上不同的对象,所以它很多时候可以代替数组名来取数组中某个位置的的对象

char array[5] = "abc";
char *ptr = array;
cout << array[2] << endl;
cout << *(ptr+2) << endl;
result:
c
c

但数组名本质上不是一个指针,我们用typeid看一看就知道了,一个int数组的数组名类型是int[n],而指针是int*

当我们把数组名放在sizeof()里面时,得到的是整个数组的大小,这一点指针无法做到。sizeof()的返回值是根据操作对象的类型决定的,对于int[n]就会返回n * sizeof(int)

const限定的指针

const限定的指针分为两种,我们用int*来举例子

第一种

int n = 3;
const int* ptr = &n;

此时我们不能透过ptr修改n,但是可以使ptr指向其他对象,也可以直接修改n的值,*ptr的值也会跟着变

第二种

int n = 3;
int * const ptr = &n;

此时我们不能使ptr指向n以外的对象,即不能修改ptr的值,但可以透过ptr修改n的值

posted @ 2021-12-27 22:22  wcvanvan  阅读(601)  评论(0)    收藏  举报