c++11 使用 std::fill 填充二维动态分配数组
c++11 using std::fill to fill a 2D dynamically allocated array
可以在此 question and also in here 中找到沿着这条线的讨论,但我的情况略有不同,因为我正在处理动态分配的内存。
另请注意,memset
不适用于 double
值。
无论如何,我正在尝试使用 std::fill
来填充动态分配的二维数组 --
#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 ;
}
方法一:据我了解,方法一应该是正确的代码,我是从头开始的&(data[0][0])
,数组的结尾应该是位于 &(data[0][0]) + (row * col * sizeof(double))
,但当我 运行 时,出现此错误,但数组已被填充 --
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 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)
Approrach 2: 然而,根据这个post,建议使用方法2,但我不太理解这段代码,因为sizeof(double)
丢失了,我得到了这个输出 --
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
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
和&(data[0][0])
应该是同一个意思吧?
方法四:我不确定这是否正确。
- 我该怎么做?
std::fill
是否比两个嵌套循环有任何额外的好处?
与堆栈分配的二维数组不同,动态二维数组不保证是一个连续的范围。然而,它是一个连续的指针范围,但数组中的每个指针可能指向非连续的内存区域。换句话说,data + i + 1
的第一个元素不一定跟在data + i
指向的数组的最后一个元素之后。如果您想知道为什么堆栈分配的二维数组是连续的,那是因为当您声明类似
的内容时
double data[10][20];
然后编译器将其理解为 10 个(连续)元素的数组,每个元素的类型为 double[20]
。后一种也是数组,保证元素连续,所以double[20]
个元素(即20个double
一个接一个)在内存中一个接一个的堆叠。 double[10][20]
与 double**
截然不同。
这就是为什么 std::fill
或 std::memset
让您头疼的原因,因为它们都假定一个连续的范围。因此,在您的情况下,嵌套循环似乎是填充数组的最简单方法。
一般来说,使用一维数组要好得多 "mimic" 二维访问,正是出于上述原因:数据局部性。数据局部性意味着更少的缓存丢失和更好的整体性能。
指针算法要求指针仅在结果仍指向同一个数组(或末尾的数组)的范围内递增。
您在 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
的单个调用以初始化整个二维数组。您要么必须多次调用 std::fill
(这违背了使用 std::fill
的目的),要么您必须创建一个知道如何处理每一行的单独分配的 Iterator 类型,或者您必须更改分配数组的方式。
我建议立即将整个数组分配为一维数组,然后编写一些额外的代码使其像二维数组一样工作。这有很多好处:
- 标准库包含一种动态分配一维数组的便捷方法:
std::vector
- 使用
std::vector
意味着您不再需要使用裸 new
和 delete
,这修复了您的代码存在的异常安全问题。
- 单个分配通常比许多分配具有更好的性能特征(当然,也有单独分配更好的情况)。
这是一个简单的包装器 class 使一维数组看起来像二维数组:
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 ;
}
}
Does std::fill
give any extra benefit over two nested loops?
使用循环的可读性较差,因为循环可以做很多其他事情,您必须花更多时间弄清楚任何特定循环在做什么。出于这个原因,人们应该总是更喜欢使用 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]
是否相同或不同。考虑表达式的类型和值。
同意以上意见。您已经分配了 10 个单独的数组,因此您无法通过单个 std::fill 调用来初始化它们。
此外,当您对非 void 类型的指针执行算术运算时,编译器会自动将您的结果乘以给定类型的 sizeof。但是,当您使用像 memset 或 memcpy 这样的函数时,您实际上必须将元素的数量乘以给定类型的 sizeof,并将其传递给这些函数之一。这是因为这些函数对字节进行操作,并且它们接受 void 类型的指针。因此,编译器不可能负责调整大小,因为 void 类型没有指定大小。
可以在此 question and also in here 中找到沿着这条线的讨论,但我的情况略有不同,因为我正在处理动态分配的内存。
另请注意,memset
不适用于 double
值。
无论如何,我正在尝试使用 std::fill
来填充动态分配的二维数组 --
#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 ;
}
方法一:据我了解,方法一应该是正确的代码,我是从头开始的&(data[0][0])
,数组的结尾应该是位于 &(data[0][0]) + (row * col * sizeof(double))
,但当我 运行 时,出现此错误,但数组已被填充 --
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 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)
Approrach 2: 然而,根据这个post,建议使用方法2,但我不太理解这段代码,因为sizeof(double)
丢失了,我得到了这个输出 --
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
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
和&(data[0][0])
应该是同一个意思吧?
方法四:我不确定这是否正确。
- 我该怎么做?
std::fill
是否比两个嵌套循环有任何额外的好处?
与堆栈分配的二维数组不同,动态二维数组不保证是一个连续的范围。然而,它是一个连续的指针范围,但数组中的每个指针可能指向非连续的内存区域。换句话说,data + i + 1
的第一个元素不一定跟在data + i
指向的数组的最后一个元素之后。如果您想知道为什么堆栈分配的二维数组是连续的,那是因为当您声明类似
double data[10][20];
然后编译器将其理解为 10 个(连续)元素的数组,每个元素的类型为 double[20]
。后一种也是数组,保证元素连续,所以double[20]
个元素(即20个double
一个接一个)在内存中一个接一个的堆叠。 double[10][20]
与 double**
截然不同。
这就是为什么 std::fill
或 std::memset
让您头疼的原因,因为它们都假定一个连续的范围。因此,在您的情况下,嵌套循环似乎是填充数组的最简单方法。
一般来说,使用一维数组要好得多 "mimic" 二维访问,正是出于上述原因:数据局部性。数据局部性意味着更少的缓存丢失和更好的整体性能。
指针算法要求指针仅在结果仍指向同一个数组(或末尾的数组)的范围内递增。
您在 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
的单个调用以初始化整个二维数组。您要么必须多次调用 std::fill
(这违背了使用 std::fill
的目的),要么您必须创建一个知道如何处理每一行的单独分配的 Iterator 类型,或者您必须更改分配数组的方式。
我建议立即将整个数组分配为一维数组,然后编写一些额外的代码使其像二维数组一样工作。这有很多好处:
- 标准库包含一种动态分配一维数组的便捷方法:
std::vector
- 使用
std::vector
意味着您不再需要使用裸new
和delete
,这修复了您的代码存在的异常安全问题。 - 单个分配通常比许多分配具有更好的性能特征(当然,也有单独分配更好的情况)。
这是一个简单的包装器 class 使一维数组看起来像二维数组:
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 ;
}
}
Does
std::fill
give any extra benefit over two nested loops?
使用循环的可读性较差,因为循环可以做很多其他事情,您必须花更多时间弄清楚任何特定循环在做什么。出于这个原因,人们应该总是更喜欢使用 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]
是否相同或不同。考虑表达式的类型和值。
同意以上意见。您已经分配了 10 个单独的数组,因此您无法通过单个 std::fill 调用来初始化它们。 此外,当您对非 void 类型的指针执行算术运算时,编译器会自动将您的结果乘以给定类型的 sizeof。但是,当您使用像 memset 或 memcpy 这样的函数时,您实际上必须将元素的数量乘以给定类型的 sizeof,并将其传递给这些函数之一。这是因为这些函数对字节进行操作,并且它们接受 void 类型的指针。因此,编译器不可能负责调整大小,因为 void 类型没有指定大小。