无条件地创建指向向量最后一个元素的指针是否合法?

5
我有以下的C++代码:
void bar(int&);
void baz();

void foo(std::vector<int>& v) {
    int* pointer_to_last = v.data() + (v.size() - 1);
    if (v.size() > 0 && *pointer_to_last == 42) {
        bar(*pointer_to_last);
    } else {
        baz();
    }
}

我正在无条件地创建一个指向向量最后一个元素的指针,但只有在向量不为空的情况下才对指针进行解引用。根据标准,这样做是否合法?或者上述代码是否存在未定义行为?
如果上述程序是合法的,那么下面的程序是否也合法?
void bar(int&);
void baz();

void foo(std::vector<int>& v) {
    int& reference_to_last = v.back();
    if (v.size() > 0 && reference_to_last == 42) {
        bar(reference_to_last);
    } else {
        baz();
    }
}

std::vector::back的参考文档中提到:

在空容器上调用back会导致未定义的行为。

所以我假设这个程序是不合法的。是否有GCC或Clang的编译器标志或任何静态分析工具可以对上述代码给出警告?


1
空向量vv.size()-1不会溢出吗?无论如何,在空向量上,我认为我们对v.data()没有太多的保证,我会支持@HolyBlackCat的观点。 - undefined
1
@eyelash back()只是对数组进行索引。与通过指针访问相比,它不会多花费一纳秒的时间。 - undefined
1
我建议你试一试,使用优化构建,并查看生成的汇编代码...我敢打赌你不会看到任何差异!而且,即使有任何差异,也会非常小,几乎不可能测量出来。也许如果你在很短的时间内(几秒钟)内紧密循环执行数百万次这样的操作,可能会有所不同。但那时你可能已经并行化了,使问题变得无关紧要。 - undefined
@eyelash 这将会被内联。 - undefined
@Oersted 在一个空向量上,v.size() - 1 会导致溢出,但由于 v.size() 是无符号的,所以这不是一个错误。错误发生在将该值添加到向量数据指针时。 - undefined
显示剩余5条评论
2个回答

5
两者都是非法的。前者被UBSAN(也称为-fsanitize=undefined)拒绝(参见[expr.add]/4
runtime error: applying non-zero offset 18446744073709551612 to null pointer

而后者被 -D_GLIBCXX_DEBUG 拒绝:
Error: attempt to access an element in an empty container.

有没有GCC或Clang的编译器标志?
上述两个,再加上`-fsanitize=address`。
`-D_GLIBCXX_DEBUG`是特定于libstdc++的。如果您使用的是带有libc++而不是libstdc++的Clang,则使用`-D_LIBCPP_ENABLE_DEBUG_MODE`。(似乎需要libc++ 17或更新版本,他们的调试模式一度出现问题。它似乎也缺少迭代器验证。:c)

能否在编译时而非运行时发现这些错误? - undefined
@eyelash 很可能不会。你可以尝试一些静态分析工具,看看它们是否能够捕捉到这个问题。 - undefined

0
根据C++20标准,行为是未定义的(7.6.6加法运算符)
(4.1)- 如果P评估为null指针值且J评估为0,则结果是null指针值。 (4.2)- 否则,如果P指向具有n个元素(9.3.4.5)的数组对象x的数组元素i,其中J的值为j,则表达式P + J和J + P指向x的(可能是假设的)数组元素i + j,如果0 ≤ i + j ≤ n,并且表达式P - J指向x的(可能是假设的)数组元素i - j,如果0 ≤ i - j ≤ n。 (4.3)- 否则,行为是未定义的。

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