首页 > 其他分享 >为何你还在坚持用数组?容器不比它香几条街?「上」

为何你还在坚持用数组?容器不比它香几条街?「上」

时间:2024-07-21 13:58:40浏览次数:17  
标签:容器 Widget int C++ 不比 数组 长度 指针

以下内容为本人的烂笔头,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/tm2OKiLBQL2GBCd2ho6i5A

C/C++ 到了寿终正寝的时候否?

最近被热议的「美国白宫提倡软件开发者放弃使用 C/C++ 这种语言再进行新的软件开发」。作为一名热衷于高性能架构开发的码农,笔者我真的很难舍弃如此经典的高性能开发语言。何况直至本文的写作时,在某些开发领域里,C/C++ 几乎是唯一选择。

之所以,C/C++ 被诟病为现代软件安全漏洞的最大隐患,就源自于 C/C++ 编写的软件允许开发人员直接操作内存,而且旧版本的语言标准一直没有提供很好的指针使用规范,导致发布的软件安全漏洞时有发生。其中涉及内存漏洞的重灾区之一就是,数组的使用。

本文就讲讲数组的毛病,和转向现代化的替代品–容器。

数组源自 C 语言标准,在「远古时期」的 C++ 标准里被大规模地被保留应用。但是,在 C++ 进入现代版本后,数组就有了完美的替代品–容器。因此数组也就变成彻底的包袱,食之无味,何不爽快弃之?

说起来也怪,现在居然还能在新代码里看到这种化石级的数组应用,可见 C++ 的现代化教育还没有真正的普及。难怪在浏览网上的 C++ 岗位招聘信息时,噼里啪啦列出一大堆要求,结尾处还不忘加上一行「需要熟悉现代版 C++」。

数组的陷阱

在使用数组时,常见的 bug 总结起来就两点:

  1. 访问越界

为了遍历或者写入数组,由于对数组长度或者元素偏移个数的计算错误,导致访问了不属于数组范围内的内存地址空间,这种错误往往导致程序崩溃或者异常关闭。

举个例子,假设为了创建九宫格的数字面板,需要存储 9 个用于展示内容的小部件,如下:

#define NUM 9

class Widget
{
public:
    Widget() {}
    ~Widget() {}
};

void create_widgets() {
    Widget *widgets[NUM];
    for (int i = 0; i <= NUM; i++)
        widgets[i] = new Widget();
}

int main() {
    create_widgets();
    return 0;
}

上面的代码中,当 i==NUMPAD_DIGITS (创建了 10 个部件,超出 9 个部件的存储空间限制) 时,widgets[i] = new Widget() 就会尝试往数组 widgets 范围外的地址空间写入数据,这是系统不允许的。所以编译上面的代码并运行,系统立马报错如下

*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)

这种错误是因为开发人员常常需要依赖人力计算清楚数组的存储空间大小,可见数组长度是一个安全隐患。

有的小伙伴可能会说,当数组存储字符串时,可以用 *char 类型指针代表数组地址,再通过 strlen() 就可以自动计算数组长度嘛。但是,这里引用的函数 strlen() 有个限制就是只能计算以空结束字符结尾的数组内容,应用范围明显受限。

  1. 占用过多空间

为了简单随意分配空间,很多时候直接定义数组,并指明元素个数。但是,又不清楚最终确切需要使用的空间大小,所以就随意指定一个比较大的元素个数。

比如,定义一个读取输入缓冲的接口,由于输入内容的数据长度未能固定,所以接口内部就随意分配一个比较大的缓冲空间:

void read_data() {
    char buf[1024];
    // read ...
}

上面这段代码,在调试运行过程中会发现很多空间被浪费没有使用到。历史遗留代码中比较耗费空间的代码往往来自上面这种接口实现,优化时应该重点关注这种代码。

  1. 指针退化

上面说到使用数组时,由于人为错误导致的访问越界问题,归根到底是依赖人力计算数组长度。虽然一般情况下,可以使用操作符 sizeof() 自动计算数组占用空间的大小(非数组内部实际存储有效数据的大小)。

但是,在数组被转存为指针之后,sizeof() 返回的就不是数组的长度了,而是指针的大小。这就是数组的指针退化的一种表现。

#include <iostream>
int test(int array[])
{
    return sizeof(array);
}

int main() {
    int arr[10];
    int *p = arr;
    std::cout << "arr size=" << sizeof(arr)
    << " fun param size=" << test(arr)
    << " pointer size=" << sizeof(p) << std::endl;
    return 0;
}

上面演示了两种将数组转存为指针的情形,分别是将数组地址赋值给同类型指针变量,和将数组作为参数输入到函数

arr size=40 fun param size=8 pointer size=8

从上面的输出来看,利用 sizeof() 操作符计算数组大小的结果已经失效,64 位计算机的指针大小就是 8。

另外,数组的指针退化还可以通过对数组比较、整体赋值等操作来观察到。

假设定义两个数据和长度都一样的数组,然后比较它们是否相等

#include <iostream>
int main() {
    int arr1[2] {1, 2};
    int arr2[2] {1, 2};
    if (arr1 != arr2) {
        std::cout << "Arrays are different!\n";
    }
    return 0;
}

输出:

Arrays are different!

从结果来看两个数据和长度都一样的数组不相等,那么为什么呢?因为在比较两个数组时,实际比较的是两个数组的地址,也就是把数组转成了指针。

再假设定义两个长度一样的数组,但是分别初始化不同的值,然后再执行赋值操作

#include <iostream>
int main() {
    int arr1[2] {1, 2};
    int arr2[2] {3, 4};
    arr1 = arr2;
    return 0;
}

实际上这样对数组赋值是无法编译通过的,也就是数组不支持整体赋值。和对数组调用比较操作符类似,在赋值表达式 arr1 = arr2 中,引用数组名时,把数组转成了指针,而且这个指针是不可修改的。

从上面的数组指针退化来看,数组的使用收到极大限制,也就是常说的功能被阉割了。当然,想要历数某样事物的缺点是可以无穷无尽的,我们的目的不是这样,而是从需求出发,找到突破。

既然数组如此落后,何不换个趁手的工具?


全文未结束,本文只是上篇。如果各位同学朋友有什么疑问可以联系笔者,当然笔者也愿意和你进一步探讨这方面的问题。另外,八戒有自己的技术圈朋友群,如果读者朋友想进群交流技术问题,欢迎联系我。下拉到文章底部有我的联系方式!

最后,非常感激各位朋友的点 「赞」 和点击 「在看」,谢谢!

标签:容器,Widget,int,C++,不比,数组,长度,指针
From: https://blog.csdn.net/2301_76403635/article/details/140453086

相关文章

  • 云原生:容器技术全解!
    一、什么是容器?容器是一种自包含、轻量级、可移植的软件打包技术,它使得应用程序可以在几乎任何地方以相同的方式运行。开发人员在自己笔记本上创建并测试好的容器,无须任何修改就能够在生产系统的虚拟机、物理服务器或公有云上运行。所谓的“自包含”是指容器镜像中完整的......
  • NumPy 广播数组是否会在二进制运算期间创建?
    我有两个numpy.ndarray具有不同形状的实例。如果我添加这两个数组,它们之间将发生广播:importnumpyasnpx=np.array([1,2,3])y=np.array([[2,3,5],[7,11,13]])print(x+y)#[[358]#[81316]]广播数组会被创建吗?也就......
  • 【软考】数据结构与算法基础 - 数组和链表
    一、数组和链表的区别(很简单,但是很常考,记得要回答全面)什么是数组:C++语言中,可以用数组,处理一组数据类型相同的数据,不可以动态定义数组的大小(使用前,必须指定大小。)在实际应用中,用户使用数组之前,无法确定数组的大小只能够将数组定义成足够大小,多余出来空间可能不被使用,......
  • 在感知器学习模型的 Python 实现中将数组传递给 numpy.dot()
    我正在尝试将单层感知器分类器的Python实现放在一起。我发现SebastianRaschka的《Python机器学习》一书中的示例非常有用,但我对他的实现的一小部分有疑问。这是代码:importnumpyasnpclassPerceptron(object):"""Perceptronclassifier.Parameters......
  • 塔子哥的最大数组-美团2023笔试(codefun2000)
    题目链接塔子哥的最大数组-美团2023笔试(codefun2000)题目内容塔子哥有一个长度为n的数组a,默认的求和方式是将a中所有元素加起来。但是塔子哥有一种技能,可以将求和的其中一次加法转换为乘法操作。在这种情况下,数组a的最大和为多少。输入描述第一行,一个正整......
  • python 舰队容器
    我正在尝试使用容器在flet中制作一个菜单,它应该是半透明的,但其中的项目不是。我尝试将opacity=1分配给元素,但没有成功-它们与容器一样透明感谢任何帮助我的代码:nickname=ft.TextField(label="xxx",hint_text="xxx")column=ft.Column(controls=[nickname......
  • 代码随想录数组二刷:长度最小的子数组(滑动窗口)
    代码随想录数组二刷:长度最小的子数组(滑动窗口)leetcode209这道题采用滑动窗口的思想去做。实现滑动窗口,主要确定如下三点:窗口内是什么?如何移动窗口的起始位置?如何移动窗口的结束位置?窗口就是满足其和≥s的长度最小的连续子数组。窗口的起始位置如何移动:如果当前窗口......
  • JAVA 数据结构 - 数组
    一、固定数组Arrays数组用来存储固定大小的同类型的元素;1、声明,初始化,访问int[]myIntArray;double[]myDoubleArr;String[]myStrArr;intsize=10;double[]myDoubleArr=newdouble[size];double[]myList={1.9,1.4,2.3,9.8};for(inti=0;i<myList.le......
  • 嵌入式学习记录——C基础(数组与排序)
    数组与排序数组一维数组二维数组排序冒泡排序选择排序数组数组是由一个或者多个相同数据类型的数据组成的一个集合一维数组如果将数组看做一个坐标轴,一维数组则如同只有X坐标,每个数组中的元素内存地址都是连续的,当数据类型和首个元素a[0]确定时,后续a[i]依次递增......
  • [LeetCode] 977. 有序数组的平方
    有序数组的平方简单给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。示例1:输入:nums=[-4,-1,0,3,10]输出:[0,1,9,16,100]解释:平方后,数组变为[16,1,0,9,100]排序后,数组变为[0,1,9,16,100]示例2:输入:nums=[-7......