【问题标题】:Why am I seeing different behavior between arrays allocated on the heap and the stack?为什么我看到分配在堆和堆栈上的数组之间的行为不同?
【发布时间】:2018-02-22 16:43:53
【问题描述】:

我正在检查 C++ 中两个二维数组的行为,一个从堆栈分配,一个从堆分配。

我创建了两个相同形状的二维数组,并用一些数据填充这些数组。然后我尝试用两种不同的方法读取数组,第一种是使用简单的数组索引格式“Arr[ROW][COLUMN]”。然后我使用指针取消引用读取数组,我得到堆分配数组的两个不同结果,但堆栈分配数组的结果相同。我试图理解为什么结果不同。我将不胜感激有人可以提供任何澄清。提前致谢。

我正在运行的代码如下:

#include <iostream>

using namespace std;

int main(){

    int rows = 6;
    int columns = 3;

    // allocate from the stack.
    double q[rows][columns];

    // allocate from the heap.
    double ** a;
    a = new double*[rows];

    for(int i = 0; i < rows; ++i){
        a[i] = new double[columns];
    }

    // populate the arrays.
    for(int i = 0; i < rows; ++i){
        for(int j = 0; j < columns; ++j){
            a[i][j] = columns*i+j;
            q[i][j] = columns*i+j;
        }
    }

    cout << "*****************" << endl;
    cout << "Array indexing method." << endl;
    cout << "*****************" << endl;

    // print the heap allocated array using array indexing.
    for(int i = 0; i < rows; ++i){
        for(int j = 0; j < columns; ++j){
            cout << a[i][j] << '\t';
        }
        cout << endl;
    }

    cout << "*****************" << endl;

    // print the stack allocated array using array indexing.
    for(int i = 0; i < rows; ++i){
        for(int j = 0; j < columns; ++j){
            cout << q[i][j] << '\t';
        }
        cout << endl;
    }

    cout << "*****************" << endl;
    cout << "Pointer dereferencing method." << endl;
    cout << "*****************" << endl;

    // print the heap allocated array.
    for(int i = 0; i < rows; ++i){
        for(int j = 0; j < columns; ++j){
            cout << *(&a[0][0] + columns*i + j) << '\t';
        }
        cout << endl;
    }

    cout << "*****************" << endl;

    // print the stack allocated array.
    for(int i = 0; i < rows; ++i){
        for(int j = 0; j < columns; ++j){
            cout << *(&q[0][0] + columns*i + j) << '\t';
        }
        cout << endl;
    }
    cout << "*****************" << endl;

    // release the memory allocated to the heap.
    for(int i = 0; i < rows; ++i){
        delete[] a[i];
    }

    delete a;

    return 0;
}

而我得到的结果是:

*****************
Array indexing method.
*****************
0       1       2
3       4       5
6       7       8
9       10      11
12      13      14
15      16      17
*****************
0       1       2
3       4       5
6       7       8
9       10      11
12      13      14
15      16      17
*****************
Pointer dereferencing method.
*****************
0       1       2
0       3       4
5       0       6
7       8       0
9       10      11
0       12      13
*****************
0       1       2
3       4       5
6       7       8
9       10      11
12      13      14
15      16      17
*****************

我可以看到,在第三个输出块中,堆分配的数组没有被正确读取,但堆栈分配的数组是。

再次感谢。

【问题讨论】:

  • double q[rows][columns]; VLA 不是标准 c++。
  • 这不是问题,但您真的需要std::endl 需要的额外内容吗? '\n' 结束一行。
  • 不,我不知道,这个例子你是对的 \n 会很好。
  • 这与在堆上分配与在堆栈上分配无关,顺便说一句。您可以在“堆”上分配一个连续的内存块。
  • 是的,只是分配arr = new double[rows * columns]; 会产生更好的结果。然后访问第 3 行,第 2 列,您可以这样做:arr[row * columns + col]

标签: c++ arrays memory-management


【解决方案1】:

&amp;q[0][0] 为您提供指向包含rowsxcolumns 双精度块的第一个双精度对象的指针。虽然&amp;a[0][0] 为您提供了一个包含columns 双精度块的第一个双精度指针(您已经使用a[0] = new double[columns]; 分配了它,还记得吗?)。所以访问它columns*i + j 会越界并且会触发未定义的行为。

【讨论】:

  • 啊,所以我的问题是我假设堆中的分配是连续的,但实际上不是?大概每一行列都非常接近其他列,因为在我的输出中,我得到了一些适当的值,而不是它们应该在的位置。
  • @JoshM。您的堆分配根本不连续。您分配 1 个包含 rows 指针计数的块指向双精度,然后分配 rows 块计数,每个块包含 columns 双精度计数。尽管在这种特殊情况下它们被一个接一个地分配(中间有一些填充)是有道理的,因为堆还没有混乱。但是,不应依赖任何关于分配器行为的此类假设。
  • 您可能会将此添加到您的答案中:“指针取消引用方法”甚至在堆栈分配的数组上调用 UB,因为它索引超出了第一个子数组的边界。即使内存是连续的,这仍然是非法的。
【解决方案2】:

数组通常是单个分配,在这种情况下位于堆栈上。数组值q[1][0] 位于q[0][columns-1] 之后。指针实现为堆中的每一行分配一个不相交的内存块。通过引用超出第一行的末尾,您处于未定义的行为领域。

【讨论】:

    【解决方案3】:

    基于堆栈的数组是完全连续的内存。您的基于堆的分配是每行一个分配,加上一个用于保存所有这些分配的分配。每个分配的内存可以以任何顺序分散在整个堆中,它们之间有任意数量的空间。如果您越过它的末尾,您不能假设向第一行分配的基数添加偏移量是有效的,并且尝试通过从先前分配的末尾走得足够远来尝试达到单独的分配肯定是一个错误。

    如果您想做您正在尝试的事情,请将您的 new[] 表达式更改为扁平化为单个数组:

    double * q = new [rows * columns];
    

    然后你可以像它是一个二维数组一样索引它。

    另外,在您的原始帖子中,您在 a 上使用了错误类型的删除调用,它也是一个数组,需要作为一个数组删除(后面带有 []。)

    【讨论】:

      猜你喜欢
      • 2023-03-29
      • 2017-02-19
      • 2014-06-01
      • 2015-10-15
      • 2013-03-26
      • 2011-10-27
      • 2020-01-28
      • 1970-01-01
      • 2014-12-05
      相关资源
      最近更新 更多