提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
本节讲一个由于下标使用错误引发的未定义错误,看似很简单的一个错误,可能反而在开发中会出错。
C++下标运算符是运算符的一种,主要用途是访问数组元素。C++将内置数组、array和string称为“拟容器”。本质上它们存储元素,本身就是一种容器,只不过都有一些限制条件或包含额外组件,所以就没有包含在容器列表里。
数组的使用几乎在每个程序中都会遇到,下标操作也是耳熟能详的。看似再自然不过的操作,仍然是隐藏一些“致命错误”,只不过不太容易被发现。
我们都知道数组下标+1并不是简单的数字+1,而是“偏移量”,这种“偏移量”和元素类型有关系,实际上是偏移了一个元素的大小。今天的错误就出现在多态场景下。
首先
代码如下(示例):
class Shape {
public:
virtual void draw() { std::cout << "shape" << std::endl; }
};
class Circle: public Shape{
public:
void draw() override { std::cout << "circle" << std::endl; }
};
这是一个很简单的类层次,Circle继承了Shape并复写了draw,如果分别创建对象会打印不同的内容。
问题来了,请看如下代码:
cout << sizeof(Shape) << sizeof(Circle) << endl;
Circle a1[10];
for (int i = 0; i < 10; ++i) {
a1[i].draw();
}
Shape *p1 = a1;
for (int i = 0; i < 10; ++i) {
p1[i].draw();
}
正常执行,没有报错。在这里因为多态特性的缘故,打印20个circle。看似对的结果能说明代码写得就没问题吗?当然不能。
我们稍微改一下:
class Shape {
public:
virtual void draw() { std::cout << "shape" << std::endl; }
};
class Circle: public Shape{
private:
int i{1};
public:
void draw() override { std::cout << "circle" << std::endl; }
};
有意思的来了,只打印了11个circle就崩溃了。那么原因何在呢?
数组下标访问是依靠偏移量来遍历的,这个偏移量是指相对于元素首地址的偏移量。假如首地址是p,对于每一个元素来讲偏移量就是p+n*sizeof(T),这里的T就是元素类型,比如在我的机器上int是4个字节(有些机器int可能不是4字节)。
所以问题来了,没有int i{1};的Circle的偏移量和Shape是一样的;然而,加了这个变量他们的偏移量就不一样了,这样算出来的地址就是错误的,访问错误的内存地址导致程序崩溃。
所以说上面没有报错的原因只是凑巧不加变量的Circle和Shape占的空间一样大,都是8。所以,这个错误被巧妙地“绕过去了”。
使用标准库的array替代内置数组,array不存在上述的转换问题。
1、整体不难,但是一不小心就出错。
2、优先使用标准库替代,增强可移植性和强保证。