C++结构对齐问题

Posted

技术标签:

【中文标题】C++结构对齐问题【英文标题】:C++ struct alignment question 【发布时间】:2009-09-21 16:29:22 【问题描述】:

我有一个预定义的结构(实际上是几个),其中变量跨越 32 位字边界。在 Linux(和使用 GCC 的 Windows)中,我可以使用“属性((打包))”将我的结构打包到正确的大小。但是,我无法使用 VC++ 和#pragma pack 使其以相同的方式工作。

使用 GCC 会返回正确的 6 字节大小:

struct

    unsigned int   a                : 3;
    unsigned int   b                : 1;
    unsigned int   c                : 15;
    unsigned int   troubleMaker     : 16;
    unsigned short padding          : 13;
 __attribute__((packed)) s;

使用 VC++ 会返回错误的 8 字节大小

#pragma pack(push)
#pragma pack(1)

struct

    unsigned int   a                : 3;
    unsigned int   b                : 1;
    unsigned int   c                : 15;
    unsigned int   troubleMaker     : 16;
    unsigned short padding          : 13;
 s;

#pragma pack(pop)

我可以通过手动将 'troubleMaker' 跨边界拆分来让事情正常工作,但我不希望这样做。有什么想法吗?

【问题讨论】:

它们不仅跨越 32 位边界,还跨越单字节边界。要获得 6 的大小,变量必须从一个字节的中间开始。我很惊讶 GCC 允许这样做。在任何情况下,如果我是你,我会删除位域,只让结构包含 6 个字符(或 3 个短裤,或其他)的数组,然后编写访问器函数来屏蔽所需的位。 【参考方案1】:

疯狂的想法:首先编写一个符合 C99 或 C++03 的程序


我建议不要使用供应商特定的 C 语言扩展来匹配设备或网络位格式。即使您使用一系列针对每个供应商的语言扩展来排列字段,您仍然需要担心字节顺序,并且您仍然有需要额外指令才能访问的结构布局。

通过使用标准化的 C API 字符串和内存复制函数以及 Posix hton 和 ntoh 函数,您可以编写一个符合 C99 标准的程序,该程序可以在任何架构或主机上以最高速度和缓存效率运行。

一个好的做法是使用以下已发布标准的函数:

C99: memcpy(), Posix: htonl(), htons(), ntohl(), ntohs()

更新:这里有一些代码应该在任何地方都可以正常工作。如果 Microsoft 仍然 还没有为 C99 实现它,您可能需要获取 <stdint.h> from this project,或者只是对 int 大小做出通常的假设。

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>

struct packed_with_bit_fields   // ONLY FOR COMPARISON
    unsigned int   a        : 3;
    unsigned int   b        : 1;
    unsigned int   c        : 15;
    unsigned int   troubleMaker : 16;
    unsigned short padding  : 13;
 __attribute__((packed));       // USED ONLY TO COMPARE IMPLEMENTATIONS

struct unpacked  // THIS IS THE EXAMPLE STRUCT
    uint32_t a;
    uint32_t b;
    uint32_t c;
    uint32_t troubleMaker;
; // NOTE NOT PACKED

struct unpacked su;
struct packed_with_bit_fields sp;
char *bits = "Lorem ipsum dolor";

int main(int ac, char **av) 
  uint32_t x;   // byte order issues ignored in both cases

  // This should work with any environment and compiler
  memcpy(&x, bits, 4);
  su.a = x & 7;
  su.b = x >> 3 & 1;
  su.c = x >> 4 & 0x7fff;
  memcpy(&x, bits + 2, 4);
  su.troubleMaker = x >> 3 & 0xffff;

  // This section works only with gcc
  memcpy(&sp, bits, 6);
  printf( sp.a == su.a
      &&  sp.b == su.b
      &&  sp.c == su.c
      &&  sp.troubleMaker == su.troubleMaker
      ? "conforming and gcc implementations match\n" : "huh?\n");
  return 0;

【讨论】:

鉴于它被标记为 C++,编写一个符合 C++03 的程序可能是一个更好的主意。 ;) 嘿,好点,但它也被标记为 gcc,它使用 C89 代码,如果 MS 实际上有一个 C 编译器,它可能是真正的目标。 :-) 我什至尝试这样做的原因是我使用的是其他人编写的遗留代码。到目前为止,我们很幸运,但是一些新消息导致了这个问题。我很确定它需要重新考虑,但我想看看是否有人对如何使其正常工作有任何有用的意见...... 当然,系统工作通常是遗留代码,我很同情。我只是以这种方式写标题来吓唬任何读到它的新人编写符合标准的代码,以便下一个可怜的人出现并维护。 :-) 我看不到您发布的代码如何在任何地方工作,因为它使用 __attribute__((packed)) 这是我遇到的具体问题......这在 vc++ 编译器中不存在.【参考方案2】:

众所周知,位域的对齐和排序是特定于实现的。声明一个普通整数字段并使用掩码和按位 (| & ^) 运算符操作“位字段”会更加安全

【讨论】:

确实如此。位域的行为在编译器上没有标准化。对于 little-endian 和 big-endian 架构,位域的行为往往不同。位域对于内部状态非常有用,但对于交换数据而言则不是因为这个原因。【参考方案3】:

我认为 Visual Studio 不支持这种行为。沉迷于 pack 宏,我尝试使用 __declspec(align(1)) 并得到相同的行为。我认为你被 12 个字节卡住了,或者重新排序了你的结构。

【讨论】:

感谢您的建议,但重新排序不是一种选择,我的结构来自外部标准。【参考方案4】:

我认为 VC++ 不支持这一点,我非常怀疑 GCC 在这方面的行为是否真的是标准的。

【讨论】:

这两种情况都不是标准的。该标准没有指定打包结构的方法。 MSVC 的 pragma 和 GCC 的属性都是非标准扩展。 几乎所有关于位域的内容都依赖于实现。 扩展 Kragen 所说的:因此两个编译器在这方面都符合标准。问题中断言 6 个字节是“正确的”和 8 个字节是“不正确的”是违反标准的。【参考方案5】:

如果它绝对需要为 6 个字节,则将其定义为 3 个短裤并自己获取数据......它不会减慢速度......无论如何编译器只是这样做......

【讨论】:

如果绝对需要 6 个字节,请使用 unsigned char[6]。 :)【参考方案6】:

只有符合代码的简短示例


struct unpacked   // apparently my other example was too long and confusing
    uint32_t a;    // ...here is a much shorter example with only the conforming
    uint32_t b;    // ...code. (The other program had the gcc-specific declaration,
    uint32_t c;    // but only for test code. Still, it was a bit long.)
    uint32_t troubleMaker;
;

struct unpacked su;
char *bits = "Lorem ipsum dolor";

void f(void) 
  uint32_t x;

  memcpy(&x, bits, 4);
  su.a = x & 7;
  su.b = x >> 3 & 1;
  su.c = x >> 4 & 0x7fff;
  memcpy(&x, bits + 2, 4);
  su.troubleMaker = x >> 3 & 0xffff;
  return 0;

【讨论】:

以上是关于C++结构对齐问题的主要内容,如果未能解决你的问题,请参考以下文章

C++ 类内存模型和对齐

C++ 结构可以在不同的编译时间有不同的对齐方式吗?

Visual Studio2008 C++结构体成员需要内存对齐吗?

C++ 结构体内存对齐

C++ 数据成员对齐和数组打包

c++数据对齐/成员顺序&继承