【问题标题】:How can I modify a C-style array as D-style array?如何将 C 样式数组修改为 D 样式数组?
【发布时间】:2016-01-18 10:35:46
【问题描述】:

问题

接受 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 样式数组的接口访问预分配的内存?

【问题讨论】:

    标签: arrays memory-management d shared-memory slice


    【解决方案1】:

    检查您的代码:

    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 但更长的length,你必须再次切片指针:

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

    或:

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

    然后,一行:

    buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];
    

    (正如你猜想的那样复制内存)变得多余,因为两者指向同一个内存块。

    是否可以仅将 D 样式数组“绑定”到 C 样式数组,并通过 D 样式数组的接口访问预分配的内存?

    是的,这正是切片指针的作用。

    【讨论】:

      【解决方案2】:

      当我执行 byte[] dStyleArray = buffer[0 .. *bufferSize] 时,是 D 分配新的内存块并将所有内容复制到 D 样式数组中,还是指向已经分配的 C 样式数组?

      它在指向。 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 样式数组的接口访问预分配的内存?

      是的,你一开始就是这样做的;)

      如果您只想更改数组的前 2 个元素,只需进行绑定并更改它们,它将“正常工作”。

      如果你想测试行为,我建议你在函数下方放置一个unittest 块,这样你就可以通过给它一个指针来测试会发生什么。此外,如果您想确保您没有进行任何 GC 分配,您可能需要考虑将 @nogc 放在您的函数上以静态检查(而 nothrow 通常对于 C 函数也是一个好主意)。

      【讨论】:

        【解决方案3】:

        当我执行 byte[] dStyleArray = buffer[0 .. *bufferSize] 时,是 D 分配新的内存块并将所有内容复制到 D 样式数组中,还是指向已经分配的 C 样式大批?

        它只是指向它。右侧的切片运算符(在概念上)与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 样式数组“绑定”到 C 样式数组,并通过 D 样式数组的接口访问预分配的内存?

        简单的右手切片:

        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
        

        我制作了 whole_array 而不是重新切片 buffer 的原因是 D 可以为我捕获边界违规。它不会对裸指针执行此操作,而是对切片指针执行此操作,因为它知道最大大小为其长度。

        如果您需要扩展缓冲区,请使用正确的 C 函数,例如 realloc 或其他什么,然后再次切出 whole_array 和 part_youre_using。

        【讨论】:

        • 切片示例给了我一个想法。如果我把整个东西切成薄片,像这样:byte[] dStyleArray = buffer[0 .. bufferMaxSize],然后像这样缩小它:dStyleArray.length = *bufferSize?一个完整的切片将允许我在不分配新内存的情况下更改dSyleArray.length(在bufferMaxSize 内)?
        • 如果*bufferSize <= bufferMaxSize,它不会分配,因为它相当于dStyleArray = dStyleArray[0 .. *bufferSize];。您可能希望使用切片语法,因为如果 *bufferSize > bufferMaxSize 会抛出 ArrayOutOfBoundException,因此您无法重新分配此语法(并且允许您创建函数 @nogc)。
        • 附加一个外部切片将始终分配,因为运行时假定超出当前长度的任何内容都可能消失。 (对于 GC 数组,它有时可以证明并非如此 - 它知道容量,因为它可以询问 gc - 但它仍然是保守的)。因此,如果您不希望切片分配新副本,请不要尝试更改长度或在切片上使用附加运算符。
        猜你喜欢
        • 2022-11-05
        • 2020-07-09
        • 1970-01-01
        • 2022-01-04
        • 2018-04-21
        • 2021-01-09
        • 1970-01-01
        • 2021-07-13
        相关资源
        最近更新 更多