使用 std::vector,为什么 &vec[0] 未定义的行为,而 vec.data() 是安全的?

With std::vector, why is &vec[0] undefined behavior, but vec.data() safe?

提问人:Zebrafish 提问时间:1/7/2018 最后编辑:Peter MortensenZebrafish 更新时间:1/8/2018 访问量:3324

问:

我一直在阅读 isocpp.org“链接此处”的常见问题解答,并遇到了以下警告:std::vector

std::vector<int> v;
auto a = &v[0]; // Is undefined behaviour but
auto a = v.data(); // Is safe

从实际网站:

void g()
{
  std::vector<Foo> v;
  // ...
  f(v.begin(), v.size());  // Error, not guaranteed to be the same as &v[0]
    ↑↑↑↑↑↑↑↑↑ // Cough, choke, gag; use v.data() instead
}

此外,如果 or 为空,则使用是未定义的行为,而使用该函数始终是安全的。&v[0]std::vectorstd::array.data()

我不确定我是否完全理解了这一点。 返回指向数组开头的指针,并返回开头的地址。我在这里没有看到区别,我不认为这是取消引用任何东西(即,没有读取元素 0 的内存)。在调试版本的 Visual Studio 上,访问下标会导致断言失败,但在发布模式下,它不会显示任何内容。此外,对于默认构造向量,这两种情况下的地址都是 0。::data()&[0]&[0][0]

另外,我不明白关于不保证与.我假设对于向量,迭代器中的原始指针 和 都是相同的值。::begin()::operator[0]begin()::data()&[0]

C++ 数组 C++11 向量 undefined-behavior

评论

3赞 JoshKisb 1/7/2018
好吧,如果它是空的,那么就没有零索引,并且数据数组可能已为向量创建,也可能没有
2赞 André 1/7/2018
关键是 v[0],即不带括号,是 UB...,因为你明确要求一个不存在的元素。
2赞 Zebrafish 1/7/2018
@Andre 换句话说,您可以指向一个无效的地址,但不能用 &.
1赞 André 1/7/2018
@Zebrafish。确切地说,您可以有一个带有无效地址的指针,并且取消引用是 UB,而不是它本身的指针。在 &v[0] 的情况下,您要求一个不存在的元素(带有 v[0]),即 UB。然后,你尝试获取它的地址(带有&),但到那时你已经触发了UB。
3赞 T.C. 1/8/2018
@MassimilianoJanes [expr.unary.op]/1 仅定义表达式实际指向对象或函数时的行为。因此,取消引用是 UB 的遗漏 - 至少在核心问题 232 实际解决之前是这样。*

答:

31赞 songyuanyao 1/7/2018 #1

我看不出这里的区别

&v[0]与 相同,即从 的第一个元素中获取地址。但是当是空的时,根本没有元素,只是导致 UB,它试图返回一个不存在的元素;试图从中获取地址是没有意义的。&(v[0])vvv[0]

v.data() 始终是安全的。它将直接返回指向基础数组的指针。当为空时,指针仍然有效(它可能是空指针,也可能不是空指针);但请注意,取消引用它(如 )也会导致 UB,与 相同。v*v.data()v[0]

我也不明白关于不保证与::begin()::operator[0]

std::vector::begin 将返回一个类型为 std::vector::iterator 的迭代器,该迭代器必须满足 RandomAccessIterator 的要求。它可能是一个原始指针,但不一定是。将其作为一个类实现是可以接受的。

评论

0赞 Zebrafish 1/7/2018
我一定是误解了引用和指针,因为我真的认为并返回了地址,该地址可以分配给指针。
6赞 songyuanyao 1/7/2018
@Zebrafish Yes 确实返回地址,问题是它试图返回不存在的元素的地址。operator&
0赞 Massimiliano Janes 1/7/2018
@songyuanyao 我删除了我的答案,因为它被否决了(我懒得:)为它辩护),但我仍然相信你答案的第一部分是不正确的;只要没有发生左值到PR值的转换,就可以取消引用空指针;因此,语言中没有任何内容禁止 vector 的潜在实现使 &v[0] 始终等于 data() ...所以你的解释要么不完整,要么不正确
1赞 songyuanyao 1/7/2018
@MassimilianoJanes 我希望你能在你的回答中更多地解释它(使用引用的标准)。:)好吧,我检查了标准,它没有定义 为空时的行为。这意味着任何行为都是可以接受的。另一方面,该标准保证即使它是空的也始终有效;我认为从标准的角度来看,这是两种情况之间最大的区别。vectorv.data()
0赞 Massimiliano Janes 1/7/2018
@songyuanyao是的,我确实同意 &v[0] 是 UB,只要 v.empty() 为 true。问题出在你的解释上,这似乎表明它之所以如此,是因为你不能“返回一个不存在的元素的地址”。AFAIK,这不是真的;请参阅 [expr.unary:1,2,3] 和 [expr.add:4,7]
2赞 Bo R 1/7/2018 #2

为了使示例更易于理解,您的问题中缺少的信息是,调用您的向量并不能保证是指针。但某些实现可能表现得足够像它,使其能够正常工作。void f(Foo* array, unsigned numFoos);.begin()Foo

在空向量情况下,返回一个指针,但您不知道它指向什么。它可能是一个 nullptr,但这并不能保证。v.data()

评论

0赞 Ruslan 1/7/2018
为了说明关于空向量的陈述:请看这个带有 g++ 的测试。请注意,在一种情况下,它返回 null 指针,在另一种情况下,它返回非 null。v.data()
1赞 StPiere 1/8/2018 #3

这一切都归结为一件简单的事情:您可以向指针添加或减去整数值,但尝试取消引用无效指针是未定义的行为。

例如,

int a[10];
int* p = a;
int* q = p + 10;   // This is fine
int r = *(p + 10)  // This is undefined behaviour

在您的示例中: 与 相同,如果向量为空,则这是一个问题。v[0]*(v's internal pointer+0)

评论

0赞 Zebrafish 1/8/2018
这是我理解的麻烦,我知道 v[0] 读取 v[0] 处的内存,但认为 &v[0] 只是获取该位置的地址。显然我错了。
0赞 StPiere 1/8/2018
v[0] 不仅提供一个值,而且还提供对某个内存位置的引用,就像它的某种窗口一样。要做到这一点,它必须适当地解释这个位置内容 - 如果它不能,那就是UB。指针只关心地址 - 它不关心某个地址下存储了哪些字节。
0赞 1/8/2018
@Zebrafish 你没有错,它没有读取任何内存,而是取消了对无效指针的引用。取消引用本身不会执行任何未点亮读取或写入此值的操作,但 C++ 仍然不允许这样做。但它可以允许这样做。从技术上讲,没有什么能阻止你的代码工作。v[0]
0赞 ttemple 1/8/2018
只要向量中有一个元素,&v[0] 就可以工作。我一直在使用这种结构,但只有在将向量调整到我需要的大小之后。调整大小会分配内存,此时 &v[0] 是完全安全的。鉴于这次讨论,我可能会从今以后切换到 .data(),但我不会回去更改旧代码......