我可以将 char* 缓冲区转换为对象类型吗?

Posted

技术标签:

【中文标题】我可以将 char* 缓冲区转换为对象类型吗?【英文标题】:Can I cast a char* buffer to an object-type? 【发布时间】:2011-01-06 10:31:19 【问题描述】:

我问这个问题是出于好奇而不是困难,因为我总是向你学习,即使是在不相关的话题上。

因此,考虑以下方法,用 C++ 编写并与 g++ 链接。这个方法工作正常,因为一切都被初始化为正确的大小。

extern "C" 
  
    void retrieveObject( int id, char * buffer )
      
        Object::Object obj;

        extractObject( id, obj );
        memcpy( buffer, &obj, sizeof(obj) );
      
  

// Prototype of extractObject
const bool extractObject( const int& id, Object::Object& obj ) const;

现在,我想避免声明本地 Object 和使用 memcpy

我尝试将retrieveObject 替换为:

void retrieveObject( int id, char * buffer )
  
    // Also tried dynamic_cast and C-Style cast
    extractObject( id, *(reinterpret_cast<Object::Object *>(buffer)) );
  

它编译和链接成功,但立即崩溃。考虑到我的缓冲区大到足以容纳Object,C++ 是否需要调用构造函数来“塑造”内存?是否有另一种方法来替换局部变量和 memcpy ?

我希望我足够清楚以便您回答,提前谢谢您。

【问题讨论】:

问题是,你为什么要这样做?这些东西在 C++ 中几乎从不需要。 (如果您要序列化到文件或网络通信,这不是解决问题的方法。) 对象创建应该涉及构造函数。在构造函数中使用缓冲区。 我想知道为什么 extractObject() 的返回类型是 const bool 以及为什么它说 const int&amp; 作为它的参数之一。有什么优势? @Thanatos & Nawaz :这段摘录来自一个非常(非常)大的软件应用程序,这段代码不是我的,我不能修改任何原型。我只需要用提取的数据填充缓冲区。 【参考方案1】:

在你的第一次努力中......

void retrieveObject( int id, char * buffer )

     Object::Object obj;
     extractObject( id, obj );
     memcpy( buffer, &obj, sizeof(obj) );
 

...您仍然让编译器创建了局部变量 obj,它保证了正确对齐。在第二次努力中……

void retrieveObject( int id, char * buffer )

     extractObject( id, *(reinterpret_cast<Object::Object *>(buffer)) );
 

...您向编译器保证缓冲区指向一个与 Object::Object 适当对齐的字节。但会吗?考虑到您的运行时崩溃,可能不会。通常,char*s 可以从任何给定的字节开始,因为更复杂的对象通常与字长对齐或与其数据成员所需的最大对齐。在 Object::Object 中读取/写入整数、双精度、指针等可能仅在内存正确对齐时才有效——这在一定程度上取决于您的 CPU 等,但在 UNIX/Linux 上,可能会产生不对齐,例如SIGBUS 或 SIGSEGV 信号。

为了解释这一点,让我们考虑一个简单的 CPU/内存架构。假设内存允许在任何给定操作中从地址 0-3、4-7 或 8-11 等读取 4 个字节(32 位架构),但您不能在地址读取 4 字节卡盘1-4, 2-5, 3-6, 5-8.... 听起来很奇怪,但这实际上是一个相当普遍的记忆限制,所以接受它并考虑后果。如果我们想在内存中读取一个 4 字节的数字 - 如果它位于那些 4 的倍数地址之一,我们可以在一次内存读取中获得它,否则我们必须读取两次:从一个包含部分的 4 字节区域数据,然后是包含其余部分的另一个 4 字节区域,然后丢弃我们不想要的位并在适当的位置重新组装其余部分,以将 32 位值放入 CPU 寄存器/内存中。这太慢了,所以语言通常会小心地将我们想要的值放在内存可以在一次操作中访问它们的地方。甚至 CPU 也是按照这种预期设计的,因为它们通常具有直接对内存中的值进行操作的指令,而无需将它们显式加载到寄存器中(即,这是甚至低于汇编/机器代码级别的实现细节)。要求 CPU 对未对齐的数据进行操作的代码通常会导致 CPU 生成中断,而操作系统可能会将其显示为信号。

也就是说,关于在非 POD 数据上使用它的安全性的其他警告也是有效的。

【讨论】:

非常感谢,这正是我正在寻找的解释。我写得很糟糕,现在我知道为什么了。 @Isaac:不客气。在您询问我对多伦回答的评论后,我在上面添加了更多解释。希望能帮助到你。干杯,托尼【参考方案2】:

您所做的是有效地序列化Object,并且当且仅当Object 中的所有数据都连续存储时才能正常工作。对于简单的对象,这可以正常工作,但是一旦有对象包含指向其他对象的指针,就会停止工作。

在 C++ 中,对象包含其他对象是极为常见的。 std::string 就是一个很好的例子。 string 类是一个容器,它引用存储在别处的引用计数器对象。所以除非你确定对象是一个简单的连续对象,否则不要这样做。

【讨论】:

这都是有效的,但与实际观察到的行为(崩溃)无关,这可能是由于缓冲区未对齐 Object::Object.... 我的课程仅由简单的公共属性组成(这将满足上述要求)。我能够用一个简单的reinterpret_cast&lt;char *&gt; 填充缓冲区,所以我猜我的类是连续存储的,对吧? Tony,你说的“错位”是什么意思?【参考方案3】:

您应该看看boost.serialisation 或boost::message_queues。 C++ 对象包含更多特定于运行时的数据 (virtual tables)。

您还应该考虑在模块之间传输对象时添加有关对象的版本信息。

【讨论】:

感谢您的回答,很遗憾我无法使用外部库。版本信息是什么意思? 如果你转储对象,你并不真正知道它们的偏移映射,例如继承被更改或添加新属性或删除旧属性。为了防止这些对象的错误重新映射,您应该检查接收对象的一种版本标记。也许是 C 结构中的 C++ 对象,您知道版本的偏移量:strunct myobj char version[4], Object data; ;【参考方案4】:

使用调试器找出崩溃的原因和位置。代码看起来还不错。

如果您想避免中间 Object 实例,那么只需避免它。让extractObject()返回一个指向Object的指针,并使用这个指向memcpy()的指针将其内容指向buffer

但是请注意,正如其他人所说,如果您只是 reinterpret_cast&lt;&gt;buffer 返回 Object,如果 Object 不够简单,事情可能会中断。

【讨论】:

不幸的是,我不能使用调试器(除非命令行 gdb...),也不能更改函数原型。不过,谢谢你的信息! 使用 GDB,如果这是您唯一可以使用的东西。您应该首先了解出了什么问题,然后才能解决它。【参考方案5】:

这可能有很多问题——首先,如果你使用一个本地对象,你不能只是构造它,然后在它上面写一些其他实例的内存(这仅适用于 POD 类型,就像它们一样不需要调用析构函数),否则很可能会导致严重的内存泄漏。

但这不是主要问题 - 您提供的解决方案可能有效,也可能无效,具体取决于所用对象的类型。它适用于简单的 POD 类型,它甚至可能适用于更复杂的类(前提是您将正确处理构造函数/析构函数调用),但它会在程序的其他部分期望对象处于原始状态时中断location - 假设你有一个类,它有 2 个成员变量:

struct A 
   int i;
   int * pi;

“pi”将始终指向“i”成员 - 如果您将该对象“memcpy”到其他位置,它很容易损坏。

【讨论】:

感谢您对内存泄漏的提醒。我知道这很糟糕,而且它在 90% 的情况下都不起作用,但就我而言,这很有效,因为我的结构足够简单。

以上是关于我可以将 char* 缓冲区转换为对象类型吗?的主要内容,如果未能解决你的问题,请参考以下文章

无法将类型为“System.Decimal”的对象强制转换为类型“System.Char[]”。

将类对象存储在 char * 缓冲区中并从缓冲区引用该对象

在Qt中如何将QString转换为const char*

java ) char类型可以自动转化成String类型吗?

将 char 转换为 int [C]

我可以在SQL Server数据库中保存“对象”吗?