c ++ 11使用std :: fill来填充2D动态分配的数组

时间:2015-12-26 22:24:08

标签: c++ arrays c++11 dynamic-memory-allocation

可以在此questionhere中找到关于此行的讨论,但我的情况略有不同,因为我正在处理动态分配的内存。

另请注意,memsetdouble值无关。

无论如何,我正在尝试使用std::fill来填充动态分配的2D数组 -

#include <iostream>
#include <algorithm>

using std::cout ; using std::endl ;
using std::fill ;

int main()
{
    double **data ;
    int row = 10, col = 10 ;
    data = new double*[row] ;
    for(int i = 0 ; i < col ; i++)
        data[i] = new double[col];

    // fill(&(data[0][0]), 
    //      &(data[0][0]) + (row * col * sizeof(double)), 1); // approach 1
    // fill(&(data[0][0]), &(data[0][0]) + (row * col), 1);   // approach 2
    // fill(data, data + (row * col * sizeof(double)), 1);    // approach 3
    // fill(&(data[0][0]), 
    //      &(data[0][0]) + 
    //       ((row * sizeof(double*)) + 
    //        (col * sizeof(double))), 1);                    // approach 4

    for(int i = 0 ; i < row ; i++) {
        for(int j = 0 ; j < col ; j++)
            cout << data[i][j] << " " ;
        cout << endl ;
    }

    for(int i = 0 ; i < row ; i++)
        delete [] data[i] ;
    delete [] data ;
}

方法1:我理解,方法1应该是正确的代码,我从头开始&(data[0][0]),数组的末尾应该位于{{1但是,当我运行时,我收到此错误,但数组已被填充 -

&(data[0][0]) + (row * col * sizeof(double))

审核2 但是,根据此post,建议使用方法2,但我不太了解此代码,因为1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 *** Error in `./test': free(): invalid next size (fast): 0x0000000000da3070 *** Aborted (core dumped) 缺失,而我我得到了这个输出 -

sizeof(double)

方法3:我不确定为什么这不会编译,1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 *** Error in `./test': free(): invalid next size (fast): 0x0000000000bf5070 *** Aborted (core dumped) data应该具有相同的含义,对吧?

方法4:我不确定这是否正确。

  1. 我该怎么办?
  2. &(data[0][0])是否会通过两个嵌套循环提供额外的好处?

3 个答案:

答案 0 :(得分:9)

与堆栈分配的2D阵列不同,动态2D阵列不能保证为连续范围。然而,它是一个连续的指针范围,但是数组中的每个指针可能指向非连续的存储区域。换句话说,data + i + 1的第一个元素可能不一定跟随data + i指向的数组的最后一个元素。如果你想知道为什么堆栈分配的2D数组是连续的,那是因为当你声明像

这样的东西时
double data[10][20];

然后编译器将其理解为10个(连续)元素的数组,每个元素类型为double[20]。后一种类型也是一个数组,它保证了连续的元素,因此double[20]元素(即20 double一个接一个地)在存储器中一个接一个地堆叠。 double[10][20]double**明显不同。

这就是std::fillstd::memset给你带来麻烦的原因,因为它们都假设一个连续的范围。因此,在您的情况下,嵌套循环似乎是填充数组的最简单方法。

通常情况下,使用一个“模拟”2D访问的一维数组要好得多,原因完全是出于上述原因:数据局部性。数据位置意味着错过的缓存更少,整体性能更好。

答案 1 :(得分:3)

指针运算要求指针仅增加到结果仍指向同一数组(或结束一个数据)的程度。

您在for循环中将每一行分配为一个单独的数组:

for(int i = 0 ; i < col ; i++)
    data[i] = new double[col]; // this creates a distinct array for each row

由于您分配的每个行数组都是col个元素,因此合法添加到&(data[0][0])的最大值为col。但是在std::fill用法的每个示例中,您向指针添加的内容多于允许使用的内容。

鉴于您分配数组的方式,您无法将原始指针传递给std::fill的单个调用,以初始化整个2D数组。您必须使用多个std::fill调用(这违背了使用std::fill的目的),或者您必须创建一个知道如何处理每行的单独分配的Iterator类型,或者您必须改变分配数组的方式。

我建议将整个数组一次分配为一维数组,然后编写一些额外的代码,使其像二维数组一样工作。这有很多好处:

  • 标准库包含一种动态分配单维数组的便捷方法:std::vector
  • 使用std::vector表示您不再需要使用裸newdelete,这可以解决代码中出现的异常安全问题。
  • 单个分配通常具有比许多分配更好的性能特征(当然,有些情况下单独分配更好)。

这是一个简单的包装类,使1D数组看起来像2D数组:

class Matrix {
  std::vector<double> data;
  int cols;

public:
  Matrix(int row, int col)
    : data(row * col)
    , cols(col)
  {}

  auto begin() { return data.begin(); }
  auto end() { return data.end(); }

  struct index_type { int row; int col; };

  double &operator[](index_type i) {
    return data[i.row * cols + i.col];
  }

  int row_count() const { return data.size()/cols; }
  int column_count() const { return cols; }
};

使用此功能,您可以重写代码:

#include "Matrix.h"

#include <iostream>
#include <algorithm>

using std::cout ; using std::endl ;
using std::fill ;

int main()
{
    Matrix data(10, 10);

    fill(data.begin(), data.end(), 1.0);

    for(int i = 0 ; i < data.row_count() ; i++) {
        for(int j = 0 ; j < data.column_count() ; j++)
            cout << data[{i, j}] << " " ;
        cout << endl ;
    }
}
  

std::fill是否可以通过两个嵌套循环获得额外的好处?

使用循环的可读性较差,因为循环可以执行许多其他操作,并且您必须花费更多时间来确定任何特定循环正在执行的操作。出于这个原因,人们应该总是更喜欢使用STL算法而不是手动循环,其他条件相同。

// fill(&(data[0][0]), 
//      &(data[0][0]) + (row * col * sizeof(double)), 1); // approach 1

指针算法自动考虑数组元素的大小。您不需要sizeof(double)。此处乘以sizeof(double)sizeof(double)内的乘以[]相同。你不会这样做:data[i * sizeof(double)],所以不要data + (i * sizeof(double))

您的示例代码使用&(data[0][0])。想想它是否与data[0]相同或不同。考虑表达式的类型和值。

答案 2 :(得分:1)

我同意上述评论。您已经分配了10个单独的数组,因此无法使用单个std :: fill调用来初始化这些数组。 此外,当您对非void类型的指针执行算术运算时,编译器会自动将结果乘以给定类型的sizeof。但是,当您使用memset或memcpy等函数时,实际上您必须将给定类型的sizeof乘以元素数并将其传递给其中一个函数。这是因为这些函数对字节进行操作,并且它们接受void类型的指针。因此,编译器无法调整大小,因为void类型没有指定的大小。