结构体中字节的顺序

7

我有点困惑关于字节在struct中的顺序是如何排列的。

假设我有以下的struct:

struct container {
    int myint;
    short myshort;
    long mylong;
};

现在,我想初始化一个类型为struct container的变量,就像以下示例一样,但是我想使用数组来实现。
struct container container1 = {.myint = 0x12345678,
                               .myshort = 0xABCD,
                               .mylong = 0x12345678};

假设intlongsizeof4,而shortsizeof2

假设没有填充。

那么struct10 bytes布局将如何呢?

这是否取决于字节序?

它会像这样吗:

0x12345678 ABCD 12345678

或者像这样:

0x78563412 CDAB 78563412

我希望你能够帮我翻译以下内容:我有一个字符数组:

char buffer[10] = {0};

我希望手动填充这个数组的数据,然后使用memcpy复制到struct中。

我应该这样做吗[1]

buffer[0] = 0x12345678 & 0xFF;
buffer[1] = 0x12345678 >> 8 & 0xFF;
buffer[2] = 0x12345678 >> 16 & 0xFF;
buffer[3] = 0x12345678 >> 24 & 0xFF;
...
buffer[9] = 0x12345678 >> 24 & 0xFF;

或者应该是[2]
buffer[0] = 0x12345678 >> 24 & 0xFF;
buffer[1] = 0x12345678 >> 16 & 0xFF;
buffer[2] = 0x12345678 >> 8 & 0xFF;
buffer[3] = 0x12345678 & 0xFF;
...
buffer[9] = 0x12345678 & 0xFF;

在我执行 memcpy 前:

memcpy(&container1, buffer, sizeof(container1);

如果我正在写入数组并将其复制到struct,它是否可移植到不同的系统上,特别是在字节序方面?

编辑: [1]在小端机器上工作,[2]在大端机器上工作吗?


5
不,它不具备可移植性。是的,它取决于字节序。而对于填充和类型大小的假设也会导致可移植性问题。 - user3386109
强调“没有可移植的方式”,这意味着在同一操作系统上从编译器到编译器都没有可移植性,更不用说在不同操作系统之间了。 - David C. Rankin
当然,这取决于字节序!在您做出“没有填充”假设之后,该问题与结构类型无关。它只是关于在内存中表示整数的问题。 - AnT stands with Russia
“myint”和“myshort”在“mylong”之前的位置是否有保证?看起来按照对齐问题将它们排序为“mylong”,“myint”,“myshort”可能更有效 - 如果实现不允许进行此优化,那将是遗憾的。 - skyking
2个回答

3
它的结果会取决于计算机字节序吗?
是的,它的结果会取决于计算机的字节序。所以你的逻辑将根据计算机的字节序而改变。
由于结构填充的原因,没有通用的方法*来解决这个问题。尽管不同的编译器提供了自定义的方法来禁用结构体填充。请参考Force C++ struct to not be byte aligned
  • You can add a static_assert (requires C11 support) just to be sure that your code doesn't compiles unless your struct is tightly packed. You won't have portable code but you still can be sure that if your code compiles, it will behave correctly.

    static_assert(sizeof(container) == sizeof(int) + sizeof(short) + sizeof(long));
    

不同编译器提供禁用结构填充的方式是不可移植的 - 每个编译器都有自己的方式,除非为了兼容性而模拟另一个编译器。您可能需要注意,static_assert 需要 C11 编译器(或符合 C99 或 C90 的编译器的非标准扩展)。 - Jonathan Leffler
@JonathanLeffler我已经在我的答案中提到了这一点,以及我链接的问题的答案都提到了OP寻求的解决方案无法移植。编辑以进一步强调它。 - bashrc
2
我对在一个仅限于C的问题中链接到一个仅限于C++的问题持有不同意见。这至少是一个警示标志。 - Jonathan Leffler
@JonathanLeffler 在 C 语言中,有一种使用宏定义实现 static_assert 的方法。请参考我的回答的最后部分。这是我多年来一直在使用的样板代码。我也看到过其他使用除了 switch 之外的东西的变体。在我看来,这是 C++ 中不需要成为内置函数的又一个 _东西_。 - Craig Estey
@bashrc 嗯,你有点需要这样做。因为你的代码可能必须在旧编译器上运行。也就是说,你为一家公司工作,该公司有一个客户坚持使用RHEL 5,而你不被允许使用/安装更新的编译器(例如,Mentor Graphics实际上与他们的客户有这个问题。他们的客户是像英特尔这样的芯片公司,软件控制着晶圆生产线。他们非常保守,因为由于最小的错误导致的一天停机成本高达1亿美元)。 - Craig Estey
显示剩余2条评论

1

我是一名有用的助手,可以翻译文本。

还有一个问题,即结构体内部元素的对齐问题。

您的结构体存在对齐间隙。 实际布局就好像您执行了以下操作:

struct container {
    int myint;
    short myshort;
    char __pad__[2];  // padding to align mylong to 4/8 byte boundary
    long mylong;
};

关于使用 union,怎么样?
union {
    struct container vals;
    char buf[10];
};

但是,为什么你想要这样做?对于我能想到的几乎任何情况,可能都有更清晰的方法来达到相同的效果。

当你说“数组”时,你是指想要初始化一个包含你的结构体的数组吗?这可以完成:

struct container conts[3] = {
    { .myint = 1, .myshort = 2, .mylong = 3 },
    { .myint = 4, .myshort = 5, .mylong = 6 },
    { .myint = 7, .myshort = 8, .mylong = 9 }
};

顺便说一下,在C中有一种方法可以使用static_assert

// compile time assertion -- if the assertion fails, we will get two case 0:'s
// which will cause the compiler to flag this
#define static_assert(_assert) \
    do { \
        switch (0) { \
        case (_assert): \
            break;
        case 0: \
            break; \
        } \
    } while (0)

使用char buf[10];不如使用char buf[sizeof(struct container)]; - alk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接