由共享指针管理的结构中的零大小数组

Zero sized array in struct managed by shared pointer

考虑以下结构:

struct S
{
    int a;
    int b;
    double arr[0];
} __attribute__((packed));

正如您所看到的,这个结构是打包的,最后有一个零大小的数组。 我想通过网络将其作为二进制数据发送(假设我处理了字节序)。

在 C/C++ 中,我可以只使用 malloc 分配我想要的 space 并在以后使用 free。

我希望此内存由 std::shared_ptr 处理。

有没有不需要特殊技巧的直接方法?

    double arr[0];
} __attribute__((packed));

C++ 中不允许零大小的数组作为数据成员(也不允许作为任何其他变量)。此外,C++中没有packed这样的属性;它是一种语言扩展(因此,它可能被认为是一种特殊的 hack)。 __attribute__ 本身就是一种语言扩展。函数属性的标准语法使用嵌套方括号,如下所示:[[example_attribute]].

I'd like to send this as binary data over the network

您可能应该正确序列化数据。有许多序列化规范,尽管其中 none 普遍存在并且 none 在 C++ 标准库中实现。事实上,网络通信也没有标准 API。

一个简单的解决方案是选择现有的序列化格式并使用实现它的现有库。

首先,让我再次解释一下为什么我有这个打包结构: 它用于网络上的数据序列化,因此有一个包含所有网络数据包结构的头文件。

我知道由于对齐问题它会生成错误的程序集,但我想这个问题在常规序列化中仍然存在(使用 memcpy 复制到 char * 缓冲区)。

我使用的 gcc 和 clang 都支持零大小数组。

这是一个完整程序的示例,其中包含我的问题的解决方案及其输出(gcc 和 g++ 的输出相同)。 使用 -O3 -std=c++17 flags

编译
#include <iostream>
#include <memory>
#include <type_traits>
#include <cstddef>

struct S1
{
    ~S1() {std::cout << "deleting s1" << std::endl;}
    char a;
    int b;
    int c[0];
} __attribute__((packed));

struct S2
{
    char a;
    int b;
    int c[0];
};

int main(int argc, char **argv)
{
    auto s1 = std::shared_ptr<S1>(static_cast<S1 *>(::operator 
              new(sizeof(S1) + sizeof(int) * 1e6)));

    std::cout << "is standart: " << std::is_standard_layout<S1>::value << std::endl;

    for (int i = 0; i < 1e6; ++i)
    {
        s1->c[i] = i;
    }

    std::cout << sizeof(S1) << ", " << sizeof(S2) << std::endl;
    std::cout << offsetof(S1, c) << std::endl;
    std::cout << offsetof(S2, c) << std::endl;

    return 0;
}

这是输出:

is standart: 1
5, 8
5
8
deleting s1

这样做有什么问题吗? 我确保使用 valgrind all allocations/frees 正常工作。

I'd like this memory to be handled by std::shared_ptr.

Is there a straight forward way of doing so without special hacks?

当然有:

shared_ptr<S> make_buffer(size_t s)
{
    auto buffer = malloc(s);                    // allocate as usual
    auto release = [](void* p) { free(p); };    // a deleter
    shared_ptr<void> sptr(buffer, release);     // make it shared
    return { sptr, new(buffer) S };             // an aliased pointer
}

这适用于放置在 malloced 缓冲区中的任何对象,而不仅仅是当有 zero-sized 数组时,前提是析构函数是微不足道的(不执行任何操作),因为它从未被调用。

当然,关于 zero-sized 数组和压缩结构的常见警告仍然适用。