为什么内置阵列不安全?

omi*_*idh 27 c++ arrays

Stanley B. Lippman撰写的C++ Primer第5版(ISBN 0-321-71411-3/978-0-321-71411-4)提到:

An [std::]array是一种更安全,更易于使用的内置阵列替代方案.

内置阵列有什么问题?

Dav*_*aim 29

  1. 内置数组是一个连续的字节块,通常在堆栈上.你真的没有办法保存有关数组,边界或状态的有用信息.std::array保留这些信息.

  2. 当从/向函数传递时,内置数组会被衰减为指针.这可能会导致:

    • 传递内置数组时,传递原始指针.指针不保留有关数组大小的任何信息.您将不得不传递数组的大小,从而uglify代码.std::array可以作为参考,复制或移动传递.

    • 无法返回内置数组,如果在该函数范围内声明了数组,最终将返回指向局部变量的指针. std::array可以安全返回,因为它是一个对象,它的生命周期是自动管理的.

  3. 你不可能在内置数组上做有用的东西,比如分配,移动或复制它们.您将结束为每个内置数组编写自定义函数(可能使用模板).std::array可以分配.

  4. 通过访问数组边界之外的元素,您将触发未定义的行为.std::array::at如果检查失败,将执行边界检查并抛出常规C++异常.

  5. 更好的可读性:内置数组涉及指针算术.std::array实现等是有用的功能front,back,beginend,以避免这种情况.

假设我想对内置数组进行排序,代码可能如下所示:

int arr[7] = {/*...*/};
std::sort(arr, arr+7);
Run Code Online (Sandbox Code Playgroud)

这不是有史以来最强大的代码.通过更改7为不同的数字,代码会中断.

std::array:

std::array<int,7> arr{/*...*/};
std::sort(arr.begin(), arr.end());
Run Code Online (Sandbox Code Playgroud)

代码更加强大和灵活.

为了清楚起见,内置数组有时会更容易.例如,许多Windows以及UNIX API函数/系统调用都需要一些(小)缓冲区来填充数据.我不会考虑我可能使用std::array的简单而不是简单的开销char[MAX_PATH].

  • `std :: sort(std :: begin(arr),std :: end(arr))`与两者完美配合 (7认同)

Lig*_*ica 25

很难衡量作者的意思,但我猜它们指的是关于原生数组的以下事实:

  • 它们是原始
    的没有.at成员函数可以用于边界检查的元素访问,尽管我反驳说你通常不想要它.要么你正在访问一个你知道存在的元素,要么你正在迭代(你可以用std::array和本机数组一样好); 如果你不知道该元素是否存在,那么边界检查访问器已经是一种非常糟糕的方法来确定它,因为它使用了错误的代码流工具,并且它带来了显着的性能损失.

  • 它们可能令人困惑
    新手倾向于忘记数组名称衰减,将数组传递到"按值"的函数,然后sizeof对随后的指针执行; 这通常不是"不安全",但会产生错误.

  • 它们不能再被分配
    ,本身并不安全,但它会导致愚蠢的人们编写具有多个指针级别和大量动态分配的愚蠢代码,然后失去记忆并犯下各种UB罪行.

假设作者推荐std::array,那将是因为它"修复"了上述所有内容,导致默认情况下代码通常更好.

但相比之下,本机阵列本质上是"不安全的"吗?不,我不会这么说.

  • 向提问者和其他C++学习者解释边界检查访问器的不足之处,在事物方案中有多少惩罚,以及什么是更好的方法是有帮助的.这假设程序员已经了解了数组的来龙去脉(包括它们与指针实际上不一样的方式),并且对数组大小调整很勤奋.大量的缓冲区溢出攻击表明我们不能依赖于此. (3认同)
  • @Jerry101:边界检查的主题已在其他地方得到很好的覆盖,不需要在这里复制!我的回答并非旨在成为关于C++的完全封装的操作指南.但是,为了您的利益而且简而言之,检测缓冲区溢出的方法不是在整个程序中添加边界检查,而是在可能的情况下使用标准库编写类型安全代码,否则使用断言并执行测试. (2认同)
  • +1用于明确声明本机数组本身不太安全.我通常使用真正的数组而不是标准库对应物,除非有充分的理由不这样做. (2认同)

Jer*_*101 6

如何std::array比内置阵列更安全,更易于使用?

很容易搞砸内置数组,特别是对于那些不是C++专家和有时会犯错的程序员的程序员.这会导致许多错误和安全漏洞.

  • 使用a std::array a1,您可以访问带边界检查a.at(i)或无边界检查的元素a[i].使用内置数组,您始终有责任努力避免越界访问.否则,代码可能会粉碎一些长时间未被注意的内存,并且变得非常难以调试.即使只是在数组边界外读取也可以用于安全漏洞,例如泄漏私有加密密钥的Heartbleed漏洞.
  • C++教程可能会假装T数组和T指针是同一个东西,然后告诉你各种异常,它们不是同一个东西.例如,结构中的T数组嵌入在结构中,而结构中指向T的指针是指向您最好分配的内存的指针.或者考虑一组数组(例如栅格图像).是否将指针自动递增到下一个像素或下一行?或者考虑一个对象数组,其中一个对象指针强制它的基类指针.所有这一切都很复杂,编译器也没有发现错误.
  • 使用a std::array a1,您可以获取其大小a1.size(),将其内容与另一个std :: array进行比较a1 == a2,并使用其他标准容器方法a1.swap(a2).使用内置数组,这些操作需要更多的编程工作,并且更容易搞乱.例如,int b1[] = {10, 20, 30};为了在没有硬编码3的情况下获得它的大小,你必须这样做sizeof(b1) / sizeof(b1[0]).要比较其内容,必须遍历这些元素.
  • 您可以通过引用f(&a1)或值f(a1)[即通过复制] 将std :: array传递给函数.传递内置数组只能通过引用进行处理,并使用指向第一个元素的指针将其混淆.那不是一回事.编译器不传递数组大小.
  • 您可以按值返回函数的std :: array return a1.返回一个内置数组会return b1返回一个悬空指针,该指针已被破坏.
  • 您可以以通常的方式复制std :: array a1 = a2,即使它包含带有构造函数的对象.如果你尝试使用内置数组,b1 = b2它只会复制数组指针(或者无法编译,具体取决于b2声明的方式).您可以使用它memcpy(b1, b2, sizeof(b1) / sizeof(b1[0])),但如果数组具有不同的大小或者它们包含带有构造函数的元素,则会破坏它.
  • 您可以轻松地更改使用std :: array的代码来使用另一个容器,如std :: vector或std :: map.

请参阅C++ FAQ 为什么我应该使用容器类而不是简单的数组?了解更多信息,例如包含具有析构函数(如std::string)或继承的C++对象的内置数组的危险.

不要害怕性能

a1.at(i)每次获取或存储数组元素时,边界检查访问需要更多指令.在一些内部循环代码中,它们会阻塞大型数组(例如,您在每个视频帧上调用的图像处理例程),这个成本可能会加起来很重要.在这种罕见的情况下,使用未经检查的访问是有意义的,a[i] 仔细确保循环代码处理边界.

在大多数代码中,您要么将图像处理代码卸载到GPU,要么边界检查成本只占总运行时间的一小部分,或者整体运行时间不是问题.同时,阵列访问错误的风险很高,从调试它所花费的时间开始.


小智 5

内置数组的唯一好处是稍微简洁的声明语法.但是std::array从水中吹出的功能性好处.我还要补充一点,这并不重要.如果你必须支持较旧的编译器,那么你当然没有选择,因为std::array它只适用于C++ 11.否则,你可以使用你喜欢的任何一个,但除非你只是简单地使用数组,你应该更喜欢std :: array只是为了让事情与其他STL容器保持一致(例如,如果你以后决定使大小动态,那该怎么办? ,并使用std :: vector,然后你会很高兴你使用,std::array因为所有你必须改变的可能是数组声明本身,其余的将是相同的,特别是如果你使用自动和其他类型推断功能C++ 11

std::array是一个模板类,它封装了一个存储在对象本身内的静态大小的数组,这意味着,如果在堆栈上实例化该类,则数组本身将位于堆栈中.它的大小必须在编译时知道(它作为模板参数传递),并且它不能增长或缩小.

数组用于存储一系列对象查看教程:http://www.cplusplus.com/doc/tutorial/arrays/

std :: vector的功能相同,但它比内置数组更好(例如:通常,当通过operator []访问元素时,vector的效率差异不如内置数组):http://www.cplusplus. COM /参考/ STL /向量/

内置数组是错误的主要来源 - 特别是当它们用于构建多维数组时.对于新手来说,他们也是混乱的主要来源.尽可能使用vector,list,valarray,string等.STL容器与内置数组没有相同的问题

因此,C++中没有理由坚持使用内置数组.内置数组采用C++,主要用于向后兼容C语言.

如果OP真的想要一个数组,C++ 11为内置数组std :: array提供了一个包装器.使用std :: array非常类似于使用内置数组对其运行时性能没有影响,具有更多功能.

与标准库中的其他容器不同,交换两个数组容器是一种线性操作,涉及单独交换范围中的所有元素,这通常是效率相当低的操作.另一方面,这允许两个容器中的元素的迭代器保持其原始容器关联.数组容器的另一个独特功能是它们可以被视为元组对象:标头重载get函数以访问数组的元素,就像它是一个元组一样,以及专门的tuple_size和tuple_element类型.

无论如何,内置数组都是通过引用传递的方式.这样做的原因是当您将一个数组作为参数传递给函数时,会传递指向它的第一个元素的指针.

当你说void f(T[] array)编译器会把它变成void f(T* array) 字符串时.C风格的字符串(即空终止的字符序列)都是通过引用传递的方式,因为它们也是'char'数组.

默认情况下,STL字符串不通过引用传递.它们就像正常变量一样.没有用于通过引用传递参数的预定义规则.即使数组总是自动通过引用传递.


vector<vector<double>> G1=connectivity( current_combination,M,q2+1,P );
vector<vector<double>> G2=connectivity( circshift_1_dexia(current_combination),M,q1+1,P );
Run Code Online (Sandbox Code Playgroud)

这也可能是复制向量,因为连接按值返回向量.在某些情况下,编译器会对此进行优化.但要确保避免这种情况,您可以将向量作为非const引用传递给连接而不是返回它们.maxweight的返回值是一个由值返回的三维向量(可以复制它).向量仅在最后插入或擦除时有效,如果要推送大量值,最好调用reserve().如果您不需要随机访问,您可以使用列表重新编写它; 使用list会丢失下标运算符,但仍然可以进行线性传递,并将迭代器保存到元素而不是下标.

对于一些编译器,使用预增量而不是后增量可以更快.除非你真的需要使用后增量,否则首选++ i到i ++.他们不一样.

无论如何,如果你没有编译优化,矢量将会非常慢.通过优化,它接近内置数组.内置数组也可能很慢,没有优化,但没有矢量那么糟糕.

  • 这并不能解决数组不安全的问题.如何"从水中吹出来"兼容"并不重要"? (2认同)