【问题标题】:How to create continuous multidimensional arrays in D, which could be interfaced with C code?如何在 D 中创建可以与 C 代码交互的连续多维数组?
【发布时间】:2016-04-27 20:45:40
【问题描述】:

我对 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();                     

asad 实际上都是动态的,并且在追加时会调整大小,但它们的行为确实不同 - 查看追加后的容量。

所以现在的问题是,我可以依赖任何这些替代方案的行为吗?至少对于new double[3][0] 语法,它是否由语言保证?是否可以保证“动态”(new double[][](0,3)) 数组的布局和连续性,只要我知道 做什么(例如不直接更改 length 属性)?

【问题讨论】:

  • 你有一个动态数组的动态数组。您会获得连续的内存占用,需要使内部维度保持静态。

标签: c multidimensional-array interface d


【解决方案1】:

您正在使用动态数组。请注意,它们有一个可以设置的长度值,在您的 GDC 示例中显示为中断nans 的零。因为您将该长度值设置为零,所以您实际上告诉它忘记它应该携带 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

【讨论】:

  • 这些零并不是真正的零,而是未初始化的值——在示例中,我使用double 类型来查看哪些数字已初始化。但你是对的,是我对 length 属性的分配改变了初始容量,我认为它不应该是理所当然的。
  • 谢谢,它可以工作,虽然我觉得奇怪的是,new double[3][1000] 中的索引顺序与 C 语法 double a[1000][3] 中的索引顺序相反,为 1000 行。还有一个问题——这个数组是分配在堆上还是栈上?
  • 正如我所说,GDC 示例中的零是长度值,实际上是零。长度永远不会未初始化。它存储在动态数组中的数据旁边。至于语法:如果double[3] 是一个包含三个双精度的数组,那么double[3][1000] 应该是一个包含一千个double[3]s 的数组。将其视为(double[3])[1000]。我不会过多地使用 C 语法,因为它甚至没有写在类型旁边。
  • D 本身也存在同样的不一致,在我编辑后的新示例顶部显示;但当然不是这里相关的语法。至于长度,我对 D 数组的内容的了解几乎为零,但我确实检查了第一个没有“%.0f”的例子,还有intsize_t 而不是double,我看到了奇怪的数字在 double 值被我的“%.0f”格式截断为 0 的地方。在我的第二个示例中,行旁边也没有长度。
  • 哦,好吧,我也在那里谈论我的领域。无论如何,更新我的答案。
【解决方案2】:

好的,感谢@weltensturm 以及更多的测试和阅读 D 的逻辑终于“点击”了,所以我总结一下。

基本上,要依赖内存中的多维动态 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];

被接受,它创建一个具有静态列数和动态行数的数组。嗯……

【讨论】:

    猜你喜欢
    • 2022-01-01
    • 2012-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-02
    • 1970-01-01
    • 2011-01-28
    • 2022-01-05
    相关资源
    最近更新 更多