包含使用 unique_ptr 的 char 数组的 C++ 对象

Posted

技术标签:

【中文标题】包含使用 unique_ptr 的 char 数组的 C++ 对象【英文标题】:C++ object containing an array of char using unique_ptr 【发布时间】:2018-05-02 20:20:36 【问题描述】:

我正在寻找一种方法来使用 unique_ptr 来分配一个结构,该结构包含一个 char 数组,其字节数动态设置以支持不同类型的消息。

假设:

struct MyMessage

    uint32_t      id;
    uint32_t      data_size;
    char          data[4];
;

如何将下面的 send_message() 转换为使用智能指针?

void send_message(void* data, const size_t data_size)

    const auto message_size = sizeof(MyMessage) - 4 + data_size;
    const auto msg = reinterpret_cast<MyMessage*>(new char[message_size]);

    msg->id = 3;
    msg->data_size = data_size;
    memcpy(msg->data, data, data_size);

    // Sending the message
    // ...

    delete[] msg;

我尝试使用以下代码使用智能点无法编译:

const auto message_size = sizeof(MyMessage) - 4 + data_size;
const auto msg = std::unique_ptr<MyMessage*>(new char[message_size]);

下面是一个完整的工作示例:

#include <iostream>
#include <iterator>
#include <memory>

using namespace std;

struct MyMessage

    uint32_t      id;
    uint32_t      data_size;
    char          data[4];
;

void send_message(void* data, const size_t data_size)

    const auto message_size = sizeof(MyMessage) - 4 + data_size;
    const auto msg = reinterpret_cast<MyMessage*>(new char[message_size]);
    if (msg == nullptr)
    
        throw std::domain_error("Not enough memory to allocate space for the message to sent");
    
    msg->id = 3;
    msg->data_size = data_size;
    memcpy(msg->data, data, data_size);

    // Sending the message
    // ...

    delete[] msg;


struct MyData

    int  page_id;
    char point_name[8];
;

void main()

    try
    
        MyData data;
        data.page_id = 7;
        strcpy_s(data.point_name, sizeof(data.point_name), "ab332");
        send_message(&data, sizeof(data));
    
    catch (std::exception& e)
    
        std::cout << "Error: " << e.what() << std::endl;
    

【问题讨论】:

您的代码原样包含未定义的行为,如果它有效,那么您很幸运(实际上很不幸,因为您认为它还可以)。 const auto msg = reinterpret_cast&lt;MyMessage*&gt;(new char[message_size]); 违反了严格的别名,memcpy(msg-&gt;data, data, data_size); 超出了数组 data 的范围,对于任何 data_size &gt; 4 这种技术在我们公司被广泛使用,我想如果你再看一遍你可能会看到 msg->data 指向一个由 new char[message_size] 分配的内存的偏移量。它可能看起来有数组的界限,但该数组确实正在访问内存中包含相关大小的位置。你觉得这有意义吗? @LessWhite 不幸的是,即使它被一家不会使该代码合法 C++ 的公司广泛使用 它看起来有点明智,但整个事情(主要是原始帖子)和解决方案感觉像是过度工程,以将 C 和 C++ 想法整合到一个地方。为什么不使用字符串?是因为它们在某些线程中使用吗?我想我在这里错过了重点。 哎呀,记错了规则。指针比较将在单个分配中起作用,但指针算术不会(即使指向同一个完整对象的其他部分也不能跨越成员边界) 【参考方案1】:

您传递给delete[] 的数据类型需要与new[] 返回的数据类型相匹配。在您的示例中,您是 new[]ing 一个 char[] 数组,但随后是 delete[]ing 一个 MyMessage 对象。那是行不通的。

简单的解决方法是更改​​这一行:

delete[] msg;

改为:

delete[] reinterpret_cast<char*>(msg);

但是,您应该使用智能指针来为您管理内存删除。但是,您提供给std::unique_ptr 的指针需要与您指定的模板参数相匹配。在您的示例中,您声明了一个std::unique_ptr,其模板参数为MyMessage*,因此构造函数期望MyMessage**,但您将其传递给char*

试试这个:

// if this struct is being sent externally, consider
// setting its alignment to 1 byte, and setting the
// size of the data[] member to 1 instead of 4...
struct MyMessage

    uint32_t      id;
    uint32_t      data_size;
    char          data[4];
;

void send_message(void* data, const size_t data_size)

    const auto message_size = offsetof(MyMessage, data) + data_size;

    std::unique_ptr<char[]> buffer = std::make_unique<char[]>(message_size);
    MyMessage *msg = reinterpret_cast<MyMessage*>(buffer.get());    

    msg->id = 3;
    msg->data_size = data_size;
    std::memcpy(msg->data, data, data_size);

    // Sending the message
    // ...

或者这个:

using MyMessage_ptr = std::unique_ptr<MyMessage, void(*)(MyMessage*)>;

void send_message(void* data, const size_t data_size)

    const auto message_size = offsetof(MyMessage, data) + data_size;

    MyMessage_ptr msg(
        reinterpret_cast<MyMessage*>(new char[message_size]),
        [](MyMessage *m) delete[] reinterpret_cast<char*>(m); 
    );

    msg->id = 3;
    msg->data_size = data_size;
    std::memcpy(msg->data, data, data_size);

    // Sending the message
    // ...

【讨论】:

实际上将data数组大小改为1很可能会额外浪费3个字节,不安全。 @Slava 除非结构对齐设置为 1 字节,这在向外部发送结构数据时是有意义的。 感谢 Remy,您的代码很棒而且效果很好。它帮助我理解了如何使用智能指针。 那么您显然需要说“需要禁用对齐”,否则您会遇到与 message_size 相同的问题,甚至更糟(因为它几乎被隐藏了) @Slava 我更改了代码以恢复原始结构,并带有注释。并使用offsetof() 而不是sizeof()【参考方案2】:

这应该可行,但仍不清楚越界访问msg-&gt;data 是否合法(但至少不比您的原始代码最差):

const auto message_size = sizeof(MyMessage) - ( data_size < 4 ? 0 : data_size - 4 );
auto rawmsg = std::make_unique<char[]>( message_size );
auto msg = new (rawmsg.get()) MyMessage;

【讨论】:

相关:C++ lacks flexible array members 这个“有效”,但只有在 message_size 至少为 sizeof(MyMessage) 时才合法(这意味着 data_size 必须 >= 4)。这也意味着如果消息需要传递给其他函数,代码现在有一个std::unique_ptr&lt;char[]&gt; 而不是std::unique_ptr&lt;MyMessage&gt; 在我看来,在处理完 msg 后,我必须调用 delete msg 以防止内存泄漏(这违背了我使用智能指针来避免内存泄漏的目的)。从这个意义上说,这个解决方案并没有给我带来任何好处。 @LessWhite 不,您不需要致电delete msgrawmsg 的析构函数会为你调用 delete[]。如果有的话,在使用 placement-new 构造对象时,您必须手动调用对象的析构函数,即msg-&gt;~MyMessage(),但在这种情况下这是可选的,因为MyMessage 仅包含以下 POD 类型不需要被破坏。如果MyMessage 包含非 POD 成员,情况会有所不同 @RemyLebeau message_size 在 OP 的代码中至少是 sizeof(MYMessage) 我假设其余代码不会改变

以上是关于包含使用 unique_ptr 的 char 数组的 C++ 对象的主要内容,如果未能解决你的问题,请参考以下文章

带有数组的 unique_ptr 有啥用吗?

带有数组的 unique_ptr 有啥用吗?

使用包含来自 Node.js 的 char 数组的结构调用 C++ dll

如何构造包含联合的结构的 unique_ptr?

std :: list可以包含不同的std :: unique_ptr ?

C ++ unique_ptr和map