如何在 D 中创建可以与 C 代码接口的连续多维数组?
How to create continuous multidimensional arrays in D, which could be interfaced with C code?
我对 D 语言产生了兴趣并开始学习它,因为我觉得它可以支持比 C 或 C++ 更简单、更高效的编码,同时保留使用以这些其他语言编写的代码的能力。
但是,在尝试构造一个二维数组时我观察到了这一点:
auto a = new double[][](1000,3); // clean and seemingly elegant
a.length = 0; // To fill the array by ~= operator
a ~= [1, 2, 3]; // fill the first row
writeln(a); // -> [[1, 2, 3]] (OK)
writeln(a.capacity,a[0].capacity); // -> 1 3 (should be 1000 3)
已经感觉不对了,因为我明确表示希望为 1000x3 数字保留内存。尽管如此,Digital Mars D 和 GCC gdc 编译器都会产生相同的结果。
所以为了使数组真正成为二维的,我又添加了一行。
a ~= [3, 4, 5]; // add another row
writeln(a); // -> [[1, 2, 3], [4, 5, 6]] (OK)
writeln(a.capacity,a[0].capacity); // -> 3 3
如果这意味着 a
的内容占用了一个大小为 3*3*double.sizeof
且行优先布局的固定内存块,这可能是可以接受的。但更多的惊喜还在路上——当我想从 C 的角度检查这个数组的布局时:
double* p = &(a[0][0]); // get a C-like pointer to the data
for(size_t i=0; i<20; i++) {
writef("%.0f ", *(p+i)); // should be: 1 2 3 4 5 6 nan nan ...
}
writeln();
我得到了令人费解的结果,并且编译器之间存在差异。 dmd with libphobos2 (version 2.071.0) 表示:
1 2 3 0 0 0 0 0 4 5 6 0 0 0 0 0 0 0 0 0
但是对于 gdc(GCC 版本 4.8.4),相同的代码打印了这个:
1 2 3 0 nan nan nan 0 nan nan nan 0 nan nan nan 0 nan nan nan 0
糟糕。这既不可用也不便携。要么数组根本不连续(所以我读了一些其他的内存),要么两种实现都有问题,因为应该保证使用 nan
进行初始化。好吧,我已经知道我可以使用 malloc
并像在 C 中那样做所有事情,但是我看不出学习另一种巨大的野兽语言有什么好处,它有自己古怪的技巧。
有没有一种干净的方法可以用 C 布局制作连续的多维数组,同时又不失去 D 的优势,如内存管理、范围、切片、算法等?
编辑:
在@weltensturm 的回答后,我修复了错误并做了更多测试。我实际上需要一个动态数组,因为我从 FIFO 中读取了数字,但我事先不知道需要多少行。这段代码显然有效:
auto as = new double[3][0]; // Inconsistent w/C: this is 0 rows by 3 columns
auto ad = new double[][](0,3); // also 0 x 3
as ~= [1,2,3]; as ~= [4,5,6];
ad ~= [1,2,3]; ad ~= [4,5,6]; // I can append to both
writeln(as); // -> [[1, 2, 3], [4, 5, 6]]
writeln(ad); // -> [[1, 2, 3], [4, 5, 6]]
writefln("as capacity: %dx%d", as.capacity, as[0].capacity);
// -> as capacity: 2x0
writefln("ad capacity: %dx%d", ad.capacity, ad[0].capacity);
// -> ad capacity: 3x3
double* p = &(as[0][0]);
for(size_t i=0; i<6; i++) writef("%.0f ", *(p+i)); // -> 1 2 3 4 5 6
writeln();
p = &(ad[0][0]);
for(size_t i=0; i<9; i++) writef("%.0f ", *(p+i)); // -> 1 2 3 4 5 6 0 0 0
writeln();
as
和 ad
实际上都是动态的,并且在附加时调整大小,但它们的行为确实不同 - 查看附加后的容量。
所以现在的问题是,我可以依赖任何这些备选方案的行为吗?语言是否至少对 new double[3][0]
语法有保证?是否可以保证 "dynamic" (new double[][](0,3)
) 数组的布局和连续性,前提是我知道 not 做什么(例如不更改 length
属性 直接)?
您正在使用动态数组。请注意,它们有一个可以设置的长度值,在您的 GDC 示例中显示为中断 nan
s 的零。因为您将该长度值设置为零,所以您有效地告诉它忘记它应该携带 1000 个元素。还允许 D 为将来的追加保留内存,因此嵌套的动态数组可能不会按照您的预期对齐。
如果要将多维数组发送到C,最好使用静态数组或连续动态数组。您使用静态数组的示例:
void main()
{
auto a = new double[3][1000];
a[0] = [1, 2, 3];
a[1] = [3, 4, 5];
double* p = &(a[0][0]);
for(size_t i=0; i<20; i++) {
writef("%.0f ", *(p+i));
}
writeln;
}
版画
1 2 3 3 4 5 nan nan nan nan nan nan nan nan nan nan nan nan nan nan
注意是千[3]个数组组成的数组,所以[0]和[1]相邻
编辑以回答您的编辑:
你有两个选项,看起来都与 C 相同。
// dynamic array of double[3]
double[3][] a;
// or one-dimensional array
double[] a;
a.reserve(1000); // ensure space for 1000 new elements (use 3000 for the second option)
a ~= [3, 5, 7]; // works for both
// pass to C using a.ptr
好的,感谢@weltensturm 以及更多的测试和阅读 D 的逻辑最终 "clicked",所以我总结一下。
基本上,要依赖内存中的多维动态 D 数组布局,除了最外层的所有维度都必须是静态的,即在编译时已知。这些声明:
double[3][] b; // allocated on the stack, or
auto b = new double[3][](0); // allocated on the heap
都理解为(长度为3的静态数组)的动态数组,并且行的大小是不允许改变的(但是行数是的):
b ~= [1,2,3]; // works
b ~= [4,5,6,7.]; // Compilation error!
这是对稳健性最好的保证。
所以在使用声明为数组时:
double a[]; // or
double[][] a; //, or
auto a = new double[][](0,3); // these will be initial dimensions, not static!
是可以的,这是自找麻烦因为D运行时允许在任何改变数组内容的操作中改变任何维度。例如,对于上述任何声明,都可以附加不均匀的行:
a ~= [1,2,3];
a ~= [4,5,6,7.]; // accepted
如果它触发内存重新分配,它也很可能会改变内存中数据的布局。
作为旁注,有趣的是关于这个的编译器错误:
auto b = new double[3][];
Error: new can only create structs, dynamic arrays or class objects, not double[3][]'s
因为它不是一个动态数组,而这个:
auto b = new double[3][0];
被接受,它生成一个具有静态列数和动态行数的数组。嗯...
我对 D 语言产生了兴趣并开始学习它,因为我觉得它可以支持比 C 或 C++ 更简单、更高效的编码,同时保留使用以这些其他语言编写的代码的能力。
但是,在尝试构造一个二维数组时我观察到了这一点:
auto a = new double[][](1000,3); // clean and seemingly elegant
a.length = 0; // To fill the array by ~= operator
a ~= [1, 2, 3]; // fill the first row
writeln(a); // -> [[1, 2, 3]] (OK)
writeln(a.capacity,a[0].capacity); // -> 1 3 (should be 1000 3)
已经感觉不对了,因为我明确表示希望为 1000x3 数字保留内存。尽管如此,Digital Mars D 和 GCC gdc 编译器都会产生相同的结果。
所以为了使数组真正成为二维的,我又添加了一行。
a ~= [3, 4, 5]; // add another row
writeln(a); // -> [[1, 2, 3], [4, 5, 6]] (OK)
writeln(a.capacity,a[0].capacity); // -> 3 3
如果这意味着 a
的内容占用了一个大小为 3*3*double.sizeof
且行优先布局的固定内存块,这可能是可以接受的。但更多的惊喜还在路上——当我想从 C 的角度检查这个数组的布局时:
double* p = &(a[0][0]); // get a C-like pointer to the data
for(size_t i=0; i<20; i++) {
writef("%.0f ", *(p+i)); // should be: 1 2 3 4 5 6 nan nan ...
}
writeln();
我得到了令人费解的结果,并且编译器之间存在差异。 dmd with libphobos2 (version 2.071.0) 表示:
1 2 3 0 0 0 0 0 4 5 6 0 0 0 0 0 0 0 0 0
但是对于 gdc(GCC 版本 4.8.4),相同的代码打印了这个:
1 2 3 0 nan nan nan 0 nan nan nan 0 nan nan nan 0 nan nan nan 0
糟糕。这既不可用也不便携。要么数组根本不连续(所以我读了一些其他的内存),要么两种实现都有问题,因为应该保证使用 nan
进行初始化。好吧,我已经知道我可以使用 malloc
并像在 C 中那样做所有事情,但是我看不出学习另一种巨大的野兽语言有什么好处,它有自己古怪的技巧。
有没有一种干净的方法可以用 C 布局制作连续的多维数组,同时又不失去 D 的优势,如内存管理、范围、切片、算法等?
编辑:
在@weltensturm 的回答后,我修复了错误并做了更多测试。我实际上需要一个动态数组,因为我从 FIFO 中读取了数字,但我事先不知道需要多少行。这段代码显然有效:
auto as = new double[3][0]; // Inconsistent w/C: this is 0 rows by 3 columns
auto ad = new double[][](0,3); // also 0 x 3
as ~= [1,2,3]; as ~= [4,5,6];
ad ~= [1,2,3]; ad ~= [4,5,6]; // I can append to both
writeln(as); // -> [[1, 2, 3], [4, 5, 6]]
writeln(ad); // -> [[1, 2, 3], [4, 5, 6]]
writefln("as capacity: %dx%d", as.capacity, as[0].capacity);
// -> as capacity: 2x0
writefln("ad capacity: %dx%d", ad.capacity, ad[0].capacity);
// -> ad capacity: 3x3
double* p = &(as[0][0]);
for(size_t i=0; i<6; i++) writef("%.0f ", *(p+i)); // -> 1 2 3 4 5 6
writeln();
p = &(ad[0][0]);
for(size_t i=0; i<9; i++) writef("%.0f ", *(p+i)); // -> 1 2 3 4 5 6 0 0 0
writeln();
as
和 ad
实际上都是动态的,并且在附加时调整大小,但它们的行为确实不同 - 查看附加后的容量。
所以现在的问题是,我可以依赖任何这些备选方案的行为吗?语言是否至少对 new double[3][0]
语法有保证?是否可以保证 "dynamic" (new double[][](0,3)
) 数组的布局和连续性,前提是我知道 not 做什么(例如不更改 length
属性 直接)?
您正在使用动态数组。请注意,它们有一个可以设置的长度值,在您的 GDC 示例中显示为中断 nan
s 的零。因为您将该长度值设置为零,所以您有效地告诉它忘记它应该携带 1000 个元素。还允许 D 为将来的追加保留内存,因此嵌套的动态数组可能不会按照您的预期对齐。
如果要将多维数组发送到C,最好使用静态数组或连续动态数组。您使用静态数组的示例:
void main()
{
auto a = new double[3][1000];
a[0] = [1, 2, 3];
a[1] = [3, 4, 5];
double* p = &(a[0][0]);
for(size_t i=0; i<20; i++) {
writef("%.0f ", *(p+i));
}
writeln;
}
版画
1 2 3 3 4 5 nan nan nan nan nan nan nan nan nan nan nan nan nan nan
注意是千[3]个数组组成的数组,所以[0]和[1]相邻
编辑以回答您的编辑:
你有两个选项,看起来都与 C 相同。
// dynamic array of double[3]
double[3][] a;
// or one-dimensional array
double[] a;
a.reserve(1000); // ensure space for 1000 new elements (use 3000 for the second option)
a ~= [3, 5, 7]; // works for both
// pass to C using a.ptr
好的,感谢@weltensturm 以及更多的测试和阅读 D 的逻辑最终 "clicked",所以我总结一下。
基本上,要依赖内存中的多维动态 D 数组布局,除了最外层的所有维度都必须是静态的,即在编译时已知。这些声明:
double[3][] b; // allocated on the stack, or
auto b = new double[3][](0); // allocated on the heap
都理解为(长度为3的静态数组)的动态数组,并且行的大小是不允许改变的(但是行数是的):
b ~= [1,2,3]; // works
b ~= [4,5,6,7.]; // Compilation error!
这是对稳健性最好的保证。
所以在使用声明为数组时:
double a[]; // or
double[][] a; //, or
auto a = new double[][](0,3); // these will be initial dimensions, not static!
是可以的,这是自找麻烦因为D运行时允许在任何改变数组内容的操作中改变任何维度。例如,对于上述任何声明,都可以附加不均匀的行:
a ~= [1,2,3];
a ~= [4,5,6,7.]; // accepted
如果它触发内存重新分配,它也很可能会改变内存中数据的布局。
作为旁注,有趣的是关于这个的编译器错误:
auto b = new double[3][];
Error: new can only create structs, dynamic arrays or class objects, not double[3][]'s
因为它不是一个动态数组,而这个:
auto b = new double[3][0];
被接受,它生成一个具有静态列数和动态行数的数组。嗯...