• C++由于错误使用下标运算符引发的未定义错误


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    前言

    本节讲一个由于下标使用错误引发的未定义错误,看似很简单的一个错误,可能反而在开发中会出错。


    一、下标运算符是什么?

    C++下标运算符是运算符的一种,主要用途是访问数组元素。C++将内置数组、array和string称为“拟容器”。本质上它们存储元素,本身就是一种容器,只不过都有一些限制条件或包含额外组件,所以就没有包含在容器列表里。

    数组的使用几乎在每个程序中都会遇到,下标操作也是耳熟能详的。看似再自然不过的操作,仍然是隐藏一些“致命错误”,只不过不太容易被发现。

    二、复现步骤

    我们都知道数组下标+1并不是简单的数字+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; }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这是一个很简单的类层次,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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    正常执行,没有报错。在这里因为多态特性的缘故,打印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; }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    有意思的来了,只打印了11个circle就崩溃了。那么原因何在呢?

    2.原因解析

    数组下标访问是依靠偏移量来遍历的,这个偏移量是指相对于元素首地址的偏移量。假如首地址是p,对于每一个元素来讲偏移量就是p+n*sizeof(T),这里的T就是元素类型,比如在我的机器上int是4个字节(有些机器int可能不是4字节)。

    所以问题来了,没有int i{1};的Circle的偏移量和Shape是一样的;然而,加了这个变量他们的偏移量就不一样了,这样算出来的地址就是错误的,访问错误的内存地址导致程序崩溃。

    所以说上面没有报错的原因只是凑巧不加变量的Circle和Shape占的空间一样大,都是8。所以,这个错误被巧妙地“绕过去了”。

    2.解决方法

    使用标准库的array替代内置数组,array不存在上述的转换问题。


    总结

    1、整体不难,但是一不小心就出错。
    2、优先使用标准库替代,增强可移植性和强保证。

  • 相关阅读:
    【Linux】进度条和git命令行
    【Linux进阶之路】Socket —— “UDP“ && “TCP“
    反射
    Linux 下的 10 个 PDF 软件
    数据治理市场:亿信华辰朝左,华傲数据向右
    Vue中调用组件使用kebab-case(短横线)命名法和使用大驼峰的区别
    利用Python将dataframe格式的所有列的数据类型转换为分类数据类型
    vue2中,vue-easytable组件的使用(一)——简介和基本使用
    mock-随机生成数据工具
    【CQF Finance Class 4 金融衍生品】
  • 原文地址:https://blog.csdn.net/jiexijihe945/article/details/130849577