C++编译器能优化at()函数的调用吗?

5
由于使用 [] 运算符进行的常规数组访问是不受检查的,因此当您的程序由于缓冲区溢出而存在远程代码执行漏洞或数据泄漏时,这并不好玩。
大多数标准数组容器包含 at() 方法,它允许对数组元素进行边界检查访问。这使得越界的数组访问被定义明确(抛出异常),而不是未定义的行为。
这基本上消除了缓冲区溢出任意代码执行漏洞,并且还有一个 clang-tidy check,警告您在索引非常量时应使用 at()。所以我在很多地方进行了更改。
大多数托管语言都有已检查的数组,它们的编译器可以在可以的情况下消除检查。
我知道 C++ 编译器可以进行很棒的优化。问题是,当它们看到不能溢出时,C++ 编译器能否消除对 at() 的调用?

1
通常情况下,当优化器发现无法到达的分支时,它会将其优化掉。问题大多是这样的 - 编译器很容易找出索引永远不会超出界限。有时候这是不可能的(例如在动态库调用之间)。 - bartop
1
你试过了吗?似乎在生成的汇编中没有任何区别:https://godbolt.org/z/mwdOR1。 - Daniel Langr
3
@user207421,不明白你的评论与此有何关联。 OP询问一个特定情况,在这种情况下可以保证不会抛出异常,并且这可以在编译时证明。 - Daniel Langr
你有处理那个异常的合理策略吗?因为未处理的异常导致应用程序崩溃而上了头条,这与其他情况一样有趣。 - n. m.
1
“我知道C++编译器可以进行很棒的优化。” - 这是一个强有力的立场。 - user7860670
显示剩余3条评论
1个回答

10

这是一个经典案例,在托管语言中可以进行边界检查消除:迭代到大小。

#include <vector>

int test(std::vector<int> &v)
{
    int sum = 0;
    for (size_t i = 0; i < v.size(); i++)
        sum += v.at(i);
    return sum;
}

当索引和大小都是常数时(可以通过常量传播解决),这并不像优化那么微不足道,它需要更高级的推理来分析值之间的关系。

正如在Godbolt上看到的那样, GCC(9.2),Clang(9.0.0)甚至MSVC(v19.22)都可以合理处理这样的代码。GCC和Clang进行自动向量化。MSVC只生成一个基本循环:

$LL4@test:
    add     eax, DWORD PTR [r9+rdx*4]
    inc     rdx
    cmp     rdx, r8
    jb      SHORT $LL4@test

这并不算太糟,但考虑到它会将使用[]而非.at()的类似循环向量化,我必须得出结论:即便在一些基本情况下我们可能认为没有问题(尤其是因为没有范围检查,所以自动向量化步骤似乎毫无理由地感到害怕),使用at也存在显著成本。如果你只针对GCC和Clang,则问题较小。在更棘手的情况下,GCC和Clang也可能会通过数据结构传递索引而变得足够混乱(这是不太可能的代码,但重点是,有时会丢失范围信息)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接