可以在此question和here中找到关于此行的讨论,但我的情况略有不同,因为我正在处理动态分配的内存。
另请注意,memset
与double
值无关。
无论如何,我正在尝试使用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:我不确定这是否正确。
&(data[0][0])
是否会通过两个嵌套循环提供额外的好处?答案 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::fill
或std::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
表示您不再需要使用裸new
和delete
,这可以解决代码中出现的异常安全问题。这是一个简单的包装类,使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类型没有指定的大小。