如何将C风格数组修改为D风格数组?

7

问题

如何以最佳方式接受C风格的数组作为参数,将其修改为D风格的数组(包括更改长度),并将其作为C风格的数组返回?


背景

我正在编写一个D库,它编译成带有C接口的DLL(我将从C++中调用我的D DLL,因此需要C接口)。 它接受byte数组并修改其内容,有时会更改数组长度

由于我正在使用C接口,我的函数必须以C风格接受数组。理想情况下,如果给定的缓冲区太小,我希望能够分配更多内存(即扩展bufferMaxSize)。

这是我的D DLL目前接受参数的方式:

// D library code; compiles to DLL with C interface.
// bufferSize is the data length, and is a pointer because I may modify the data length.
// bufferMaxSize is the total allocated buffer size.
export extern(C) void patchData(const size_t bufferMaxSize, size_t * bufferSize, byte * buffer) { ... }

在我的D库中,已经存在接受D风格数组的代码。在某个地方,需要将C风格数组转换为D风格数组。
我目前是这样进行转换的(简化示例):
// D library code; compiles to DLL with C interface.
export extern(C) void patchData(const size_t bufferMaxSize, size_t * bufferSize, byte * buffer) {
    // Convert from C-style array to D-style.
    byte[] dStyleArray = buffer[0 .. *bufferSize];

    // Modify data.
    dStyleArray[0] = cast(byte) 0xab;
    dStyleArray[1] = cast(byte) 0xbc;

    dStyleArray.length = dStyleArray.length + 16;

    // Return modified data as C-style array.
    buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];
    *bufferSize = dStyleArray.length;
}

代码能正常运行,但是我不确定到底发生了什么。我的主要关注点是速度,如果我在循环中使用这个函数,我不希望不断地分配新的内存并来回复制它的内容

当我执行 byte[] dStyleArray = buffer[0 .. *bufferSize] 时,D语言是否会分配一个新的内存块并将所有内容复制到D风格的数组中,还是只是指向已经分配的C风格数组?

当我执行 dStyleArray.length = dStyleArray.length + 16 时会发生什么?由于 dStyleArray 是从 buffer 切片而来的,我现在是在分配新的内存或者复制内存吗?还是扩展到了 buffer 中?

当我执行 buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length]; 时,我正在复制内存,对吗?

是否可以将D风格的数组与C风格的数组"绑定",通过D风格数组的接口访问预先分配的内存?

3个回答

5
当我执行byte [] dStyleArray = buffer [0 .. * bufferSize]时,D会分配一个新的内存块并将所有内容复制到D-style数组中,还是指向已经分配的C-style数组?
它是指向已经分配的C-style数组。Phobos也使用相同的技巧将C“字符串”转换为D字符串: https://github.com/D-Programming-Language/phobos/blob/67c95e6de21d5d627e3c57128b4d6e332c82f785/std/string.d#L208-L211 当我执行dStyleArray.length = dStyleArray.length + 16时,发生了什么?由于dStyleArray是从缓冲区切片的,现在是否正在分配新内存/复制内存?还是扩展到缓冲区中?
那可能不是你想要/期望的。它将在垃圾回收内存上分配一个新块,并将内容复制到其中。它没有任何扩展它的方式,因为运行时没有关于内存块的任何信息(它没有管理它)。你真的想要扩展缓冲区,还是移动指针(这将在D中进行切片)?
当我执行buffer [0..dStyleArray.length] = dStyleArray [0..dStyleArray.length];时,我正在复制内存,对吗?
是的。它被降级为memcpy。
是否可能只是将D样式数组“绑定”到C样式数组,并通过D样式数组的接口访问预分配的内存?
是的,这就是你一开始做的 ;)
如果您只想更改数组的前两个元素,请进行绑定并更改它们,它将“正常工作”。
如果您想测试行为,我建议您在函数下面放置一个unittest块,这样您就可以通过给它一个指针来测试发生了什么。此外,如果您想确保没有进行任何GC分配,您可能需要考虑在函数上放置@nogc以进行静态检查(对于C函数,nothrow通常也是个不错的选择)。

4
当我执行byte[] dStyleArray = buffer[0 .. *bufferSize]时,D是在分配新的内存块并将所有内容复制到D-style数组中,还是指向已经分配的C-style数组?
它只是指向它。右侧的切片运算符在概念上与array.pointer = &first_element; array._length = length;相同,这是一个非常快速和简单的操作(顺便说一句,我称之为_length而不是length,因为设置长度属性实际上可能会调用一个函数,接下来会讲到)。
当我执行dStyleArray.length = dStyleArray.length + 16时会发生什么?
这将分配新的内存。当长度扩展时,除非运行时可以证明它是安全的(或者您告诉它假设它是安全的,并且它知道它来自GC),否则数组将被复制到新位置。它基本上在指针上调用realloc(),但并不是字面意义上的,它与C realloc不兼容。
由于它来自C,运行时只知道它不拥有内存,它在其他地方进行了管理,并且在尝试扩展时始终会分配一个新的内存。如果您想以其他方式扩展它,请自己扩展。
当我执行buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];时,我是在复制内存,对吗?
对的,在左侧进行了切片,因此会进行复制。
是否可能只将D-style数组“绑定”到C-style数组,并通过D-style数组的接口访问预先分配的内存?
裸右手切片:
auto d_array = c_array[0 .. c_array_length];
它可以处理除长度扩展之外的所有内容。它保留指针,因此写入元素将立即影响原始元素。 (顺便说一句,由于它是共享的C内存,请确保在D仍在使用它时不要free它!只要在这个函数中仅使用它而不将切片存储在任何地方,您应该没问题。)
如果您确实需要扩展长度,则需要自己扩展。我喜欢的方法是将整个潜在数组切片为完全容量,然后再次将其切片以获得有限容量的窗口。
所以像这样:
auto whole_array = buffer[0 .. bufferMaxSize]; // assuming buffer is already fully allocated on the C side
auto part_youre_using = whole_array[0 .. *bufferSize];

// to extend:
*bufferSize += 16; // extend the size
part_your_using = whole_array[0 .. *bufferSize]; // and reslice from the original

我制作整个数组而不是重新切片缓冲区的原因是为了让D帮我捕捉边界违规的错误。裸指针不能做到这一点,但是分片指针可以,因为它知道最大大小作为其长度。
如果您需要扩展缓冲区,请使用正确的C函数,例如realloc或其他函数,然后再次切片整个数组和所需部分。

这个切片示例给了我一个想法。如果我像这样切整个东西:byte[] dStyleArray = buffer[0 .. bufferMaxSize],然后像这样缩小它:dStyleArray.length = *bufferSize,会怎么样呢?完整的切片将允许我在不分配新内存的情况下更改 dSyleArray.length(在 bufferMaxSize 范围内)吗? - midrare
如果 *bufferSize <= bufferMaxSize,它不会分配内存,因为它等价于 dStyleArray = dStyleArray[0 .. *bufferSize];。 您可能更喜欢切片语法,因为如果 *bufferSize > bufferMaxSize,它将抛出 ArrayOutOfBoundException,因此您无法使用此语法重新分配(并且这使函数成为 @nogc)。 - Geod24
将一个外部的切片追加到另一个切片时,总是会进行分配操作,因为运行时会假设超出当前长度的任何内容都可能不存在了。(对于带有垃圾回收机制的数组,有时候它可以证明容量没有达到极限,因为它可以向垃圾回收机制询问,但这仍然是保守的)。因此,如果您不想让它分配新的副本,请不要尝试更改长度或在切片上使用追加运算符。 - Adam D. Ruppe

4

检查您的代码:

byte[] dStyleArray = buffer[0 .. *bufferSize];

这看起来不错。注意,这不会分配内存。D数组本质上是指针和长度,因此这一行等效于以下伪代码

struct DByteArray { byte* ptr; size_t length; }
DByteArray dStyleArray;
dStyleArray.ptr = buffer;
dStyleArray.length = *bufferSize;

从这里开始,访问 dStyleArray 的元素将访问由 buffer 指向的相同数据,这意味着:

dStyleArray[0] = cast(byte) 0xab;
dStyleArray[1] = cast(byte) 0xbc;

将“修改buffer”也会受到影响。

更进一步:

dStyleArray.length = dStyleArray.length + 16;

对于 D 动态数组,增加其 length 将导致重新分配内存。D 运行时将使用由 dStyleArray 所切片的内存,并将其复制到新分配的内存块中。如果要使 dStyleArray 指向第一个 byte 但长度更长,则必须再次切片指针:

dStyleArray = dStyleArray.ptr[0 .. dStyleArray.length + 16];

或者:

dStyleArray = buffer[0 .. *bufferSize + 16];

然后,这一行代码:
buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];

(就像你猜的那样)复制内存的操作变得多余了,因为这两个指针指向同一块内存。

能否只是将D风格数组“绑定”到C风格数组,通过D风格数组的接口访问预先分配的内存呢?

是的,这正是对指针进行切片的操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接