|
如果我们定义一个长度为 n 的数组,那么理论上下标的范围为 [0,n - 1] 。
但是实际上,在C/C++中,数组的下标可以是负数,而且是可以通过编译的。
int arr[] = { 1,2,3,4 };
int num = arr[-3];
这里需要明确,C/C++ 是不会检查下标时候合法。
因为C要追求速度,如果他要检查下标,就需要在运行的时候,时刻检查下标是否落在 [0,n - 1] 的位置中。
我们可以这样理解,下面两条语句是等价的
arr[2];
*(arr + 2);
也就是 [ \ \ \ ] 是会展开成首地址加一个偏移量。
当然这里有一个很沙雕的写法。

我们要明白,C/C++的代码最终会转换到汇编。

上面的代码可以理解为,在 a,b,c 对应着 [rbp - 4],[rbp - 8],[rbp - 12] 的内存单元。
这里我们发现,相邻的变量,在物理内存上也是相邻的。
所以我们有个邪恶的想法,用指针偷偷的修改a的值。

注意,这样的行为属于未定义行为,每个编译器的实现都不一样。
到这里我们可以发现,C/C++对于运行期基本就是放任的。
那么这里,我们可以举一个例子。C++中的类的公有私有属性是编译器的内容,在运行期的时候是不检查的。
因此我们可以写出这样的代码

解释一下为什么
A的内存结构为

这个时候我们强制用一个int * 的指针指向 a
int* p = (int*)&a;

然后我们通过指针修改
*p = 114514;

那就修改了x的值,修改y的值也是同理。
推荐阅读严格鸽:C++虚函数表的位置——从内存的角度
当然这样玩基本上都是UB(未定义行为)

大家可以自行理解为什么会这样
不过有的时候,负数下标还是有点用的

这里需要明白,c++的设计思想——零成本抽象。
也就是你定义一个数组 arr[] 和你定义一堆变量 arr_0,arr_1,arr_2..... 的性能是一样的。
而c++在运行的时候是完全放任,什么都不管,这就是为什么c++没有gc而诸如Java,go等语言有。
因为c++相信程序员,我不检查下标是否越界是因为我相信你不会越界,或者你越界是有目的的。你不回收内存,也一定有自己的目的( |
|