【问题标题】:C++ casting a struct to std::vector<char> memory alignmentC++ 将结构转换为 std::vector<char> 内存对齐
【发布时间】:2018-06-28 22:28:33
【问题描述】:

我正在尝试将结构转换为 char 向量。 我想发送我在 std::vector 中转换的结构,抛出一个 UDP 套接字并将其转换回另一侧。这是我的带有 PACK 属性的结构。

#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
PACK(struct Inputs
{
    uint8_t structureHeader;
    int16_t x;
    int16_t y;
    Key inputs[8];
});

这里是测试代码:

auto const ptr = reinterpret_cast<char*>(&in);
std::vector<char> buffer(ptr, ptr + sizeof in);
//send and receive via udp
Inputs* my_struct = reinterpret_cast<Inputs*>(&buffer[0]);

问题是: 除我的 uint8_t 或 int8_t 外,一切正常。 我不知道为什么,但无论何时何地我在结构中放置一个 1Bytes 值, 当我将其回退时,该值不可读(但其他值是) 我尝试只输入 16 位值,即使使用 最大值,所以所有位都可以。

我认为这是内存中字节对齐的问题,但我不知道如何使它工作。

谢谢。

【问题讨论】:

  • 第 1 步:删除所有宏和强制转换。第 2 步:使用序列化库,例如 ceral。第 3 步:利润。
  • 你想做的事情是错误的,原因有很多。除非它们都使用完全相同的编译器(也是相同的版本)编译并在完全相同的平台和相同的架构中运行,否则没有任何东西可以保证对方将获得相同的结构。
  • 有没有不使用外部库的序列化方法?这是一个项目,我对库有很多限制。
  • 逐个成员、逐个字节地序列化成员。
  • 当双方都理解并解决了实体序列化的局限性时,它没有任何问题。当然比任何“正确”的序列化都要快。不过,我不清楚这个问题。 '当我将 2 个字节放入 uint8_t' 是什么意思?

标签: c++ vector struct casting reinterpret-cast


【解决方案1】:

我正在尝试将结构转换为 char 向量。

您不能将任意对象转换为向量。您可以将对象转换为 char 数组,然后将该数组复制到向量中(这实际上是您的代码正在执行的操作)。

auto const ptr = reinterpret_cast<char*>(&in);
std::vector<char> buffer(ptr, ptr + sizeof in);

第二行定义了一个新的向量,并通过复制代表你的对象的字节来初始化它。这是合理的,但它与你所说的你试图做的不同。

我认为这是内存中字节对齐的问题

这是很好的直觉。如果您没有告诉编译器打包该结构,它会插入填充字节以确保每个字段从其自然对齐开始。该操作不可逆的事实表明,接收端的包装方式并不完全相同。您确定接收程序具有完全相同的打包指令和结构布局吗?

在 x86 上,您可以使用未对齐的数据,但在访问未对齐的成员变量时可能会付出很大的性能成本。将包装设置为一个,并且第一个字段的大小为奇数,您可以保证下一个字段将不对齐。我敦促你重新考虑这一点。设计结构,使所有字段都落在它们的自然对齐边界上,并且您不需要调整包装。这可能会使你的结构体更大一点,但它会避免所有的对齐和性能问题。

如果您想在有线格式中省略填充字节,您必须将相关字段逐字节复制到有线格式中,然后在接收端将它们复制回来。

关于:

#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )

以下划线和大写字母或两个下划线开头的标识符保留用于“实现”,因此您可能不应该使用__Declaration__ 作为宏的参数名称。 (“实现”是指编译器、标准库和编译器所需的任何其他运行时位。)

【讨论】:

    【解决方案2】:

    1

    vector 类具有动态分配的内存并在内部使用指针。所以不能发送向量(但是可以发送底层数组)

    2

    SFML 有一个很棒的类,叫做 sf::packet。它是免费、开源和跨平台的。

    我最近正在开发一个用于其他个人项目的个人跨平台套接字库,但我最终为了 SFML 而退出了它。要测试的东西太多了,我把所有的时间都花在测试上以确保一切正常,而没有在我想做的实际项目上完成任何工作。

    3

    memcpy 是你最好的朋友。它被设计成可移植的,您可以利用它来发挥自己的优势。

    你可以用它来调试。 memcpy 你想看到的东西到一个 char 数组中并检查它是否符合你的期望。

    4

    为了避免进行大量的稳健性测试,请将自己限制为仅使用字符、32 位整数和 64 位双精度数。你使用不同的编译器? struct 打包依赖于编译器和体系结构。如果您必须使用打包结构,则需要保证打包在您将使用的所有平台上按预期工作,并且所有平台都具有相同的字节序。显然,这就是您遇到的麻烦,很抱歉,我无法为您提供更多帮助。如果我尝试制作便携式套接字,我会建议定期序列化,并且肯定会避免结构打包。

    如果你能做出我提到的那些保证,那么在 LINUX 上发送真的很容易。

    // POSIX
    void send(int fd, Inputs& input)
    {
        int error = sendto(fd, &input, sizeof(input), ..., ..., ...);
        ...
    }
    

    winsock2 使用 char* 而不是 void* :(

    void send(int fd, Inputs& input)
    {
        char buf[sizeof(input)];
        memcpy(buf, &input, sizeof(input));
        int error = sendto(fd, buf, sizeof(input), ..., ..., ...);
        ...
    }
    

    【讨论】:

      【解决方案3】:

      你有没有试过最简单的方法:

      unsigned char *pBuff = (unsigned char*)&in;
      for (unsigned int i = 0; i < sizeof(Inputs); i++) {
          vecBuffer.push_back(*pBuff);
          pBuff++;
      }
      

      这适用于打包和非打包,因为您将迭代 sizeof。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-11
        • 2014-02-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多