标准移动错误?如果将赋值副本嵌套在迭代多次的循环中,则向量引用的标准向量赋值会出现堆错误

std move bug? heap error with std vector assignment of a vector reference if nesting the assignment copy within a loop that iterates more than once

我遇到堆运行时错误。我怀疑它是过度使用 move 时的编译器错误。 代码的简化版本是

    vector<vector<int>>result{{1,2,3}};

    int size = result.size();
    for(auto jdx = size-1; jdx >= 0; --jdx){
        vector<int> &row = result[jdx];
        // vector<int> newrow(row);
        for(int idx = 2; idx >=1; --idx) {
            vector<int> newrow(row);
            result.push_back(newrow);
        }
    }

如果我将 newrow(row) 行换出最内层的循环,就可以像下面这样, 矢量<矢量>结果{{1,2,3}};

    int size = result.size();
    for(auto jdx = size-1; jdx >= 0; --jdx){
        vector<int> &row = result[jdx];
        vector<int> newrow(row);
        for(int idx = 2; idx >=1; --idx) {
            //vector<int> newrow(row);
            result.push_back(newrow);
        }
    }

转储错误如下

=================================================================
==31==ERROR: AddressSanitizer: heap-use-after-free on address 0x6040000000b0 at pc 0x00000034789d bp 0x7ffd3434d6b0 sp 0x7ffd3434d6a8
READ of size 8 at 0x6040000000b0 thread T0
    #4 0x7f9a26d790b2  (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
0x6040000000b0 is located 32 bytes inside of 48-byte region [0x604000000090,0x6040000000c0)
freed by thread T0 here:
    #5 0x7f9a26d790b2  (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
previously allocated by thread T0 here:
    #6 0x7f9a26d790b2  (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
Shadow bytes around the buggy address:
  0x0c087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c087fff8000: fa fa fd fd fd fd fd fd fa fa 00 00 00 00 00 06
=>0x0c087fff8010: fa fa fd fd fd fd[fd]fd fa fa fa fa fa fa fa fa
  0x0c087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==31==ABORTING

在这里,我最初对您的代码所做的评论:

you keep a reference on an element of the vector, then you extend this vector, which can lead to reallocation. This reference becomes invalid

另一条评论 the link 解释了这种行为。

下面是一个最小的例子来明确说明相同的情况。

我们可以看到在扩展向量之后,整个存储在堆上的其他地方(元素已被移动)但引用仍然引用索引 2 处元素的先前地址。

/**
  g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
      -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
      -g -O0 -UNDEBUG -fsanitize=address,undefined

  $ ./prog_cpp 
  v from 0x602000000010 to 0x602000000020
  elem at 0x602000000018 is 30
  v from 0x603000000010 to 0x603000000024
  elem at 0x602000000018=================================================================
  ==186890==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000018 at pc 0x55f3af7583e1 bp 0x7ffd22315d80 sp 0x7ffd22315d70
  READ of size 4 at 0x602000000018 thread T0
  ...
**/

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#include <iostream>
#include <vector>

int
main()
{
  auto v=std::vector<int>{10, 20, 30, 40};
  std::cout << "v from " << data(v) << " to " << data(v)+size(v) << '\n';
  const auto &elem=v[2];
  std::cout << "elem at " << &elem << std::flush << " is " << elem << '\n';
  v.emplace_back(50);
  std::cout << "v from " << data(v) << " to " << data(v)+size(v) << '\n';
  std::cout << "elem at " << &elem << std::flush << " is " << elem << '\n';
  return 0;
}