打包或不打包仅包含数组的结构

To pack or not to pack a structure containing just an array

实验: 让我们在 c/c++ 中声明一个 SHA-512 摘要容器(使用 GCC):

#define DIGEST_LENGTH 512
struct Digest {
  uint32_t bits[DIGEST_LENGTH / 8 / sizeof(uint32_t)];
} __attribute__((packed));

让我们不要争论选择 uint32_t 数组而不是 char 数组。顺其自然吧。

然后我们可以按如下所示的方式从工作缓冲区中读入摘要:

Digest digest;
......
memcpy(&digest, buffer, sizeof(Digest));

类似地,我们可以将摘要写入工作缓冲区:

memcpy(buffer, &digest, sizeof(Digest)); //Assuming sufficient buffer size

我的问题:

一个。 sizeof(Digest) 总是 return 正确大小(= 512 位或 64 字节)的 packed 属性的必要和充分条件吗?

乙。 digest->bits[i] 在我们保留 packed 属性的同时在所有架构上都是安全的操作吗?

C。我们能否在保持容器不透明的同时简化表示?

D.如果我们保留代表,是否需要支付 运行 时间罚款?

我知道还有其他关于 packed 属性的问题,但我的问题是专门针对包含单个基本类型数组的结构。

A. Is the packed attribute necessary and sufficient condition for sizeof(Digest) to always return the correct size (= 512 bits or 64 bytes)?

够用了

B. Is digest->bits[i] a safe operation on all architectures while we keep the packed attribute?

我认为你不明白__attribute__((packed))。以下是实际执行的操作。

When packed is used in a structure declaration, it will compress its fields such, such that, sizeof(structure) == sizeof(first_member) + ... + sizeof(last_member).

这里是url上述语句的资源Effects of __attribute__((packed)) on nested array of structures?

编辑:

当然安全了。打包定义内存中的布局,但不要担心,因为即使数据未对齐,访问特定数据类型也会由编译器处理。

C. Can we simplify the representation while keeping the container opaque?

是的,您可以只定义一个简单的缓冲区 uint32_t bits[LENGTH];,它将以同样的方式为您工作。

D. Is there a run-time penalty to pay if we keep the representation?

一般来说是的。打包强制编译器不在成员之间的数据结构中执行填充。数据结构中的填充使物理对象更大,但对奇异字段的访问更快,因为它只是读取操作,不需要读取、掩码和旋转等。

请检查下面这个非常简单的程序,它显示了打包对结构大小的影响。

#include <stdio.h>
#include <stdint.h>

#pragma pack(push, 1) 
typedef struct _aaa_t {
  uint16_t a;
  uint8_t b;
  uint8_t c;
  uint8_t d;
} aaa_t;
#pragma pack(pop)

typedef struct _bbb_t {
  uint16_t a;
  uint8_t b;
  uint8_t c;
  uint8_t d;
} bbb_t;

int main(void) {
    aaa_t a;
    bbb_t b;
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof(b));
    printf("%p\n", &(a.a));
    printf("%p\n", &(a.b));
    printf("%p\n", &(a.c));
    printf("%p\n", &(a.d));
    printf("%p\n", &(b.a));
    printf("%p\n", &(b.b));
    printf("%p\n", &(b.c));
    printf("%p\n", &(b.d));
    return 0;
}

程序输出:

5
6
0xbf9ea115
0xbf9ea117
0xbf9ea118
0xbf9ea119
0xbf9ea11a
0xbf9ea11c
0xbf9ea11d
0xbf9ea11e

解释:

Packed:
     ____________ _______ _______ _______ _______
    |            |       |       |       |       |
    | 0xbf9ea115 | msb_a | lsb_a | lsb_b | lsb_c |
    |____________|_______|_______|_______|_______|
    |            |       |
    | 0xbf9ea119 | lsb_d |
    |____________|_______|

Not Packed:
     ____________ _______ _______ _______ _______
    |            |       |       |       |       |
    | 0xbf9ea11a | msb_a | lsb_a | lsb_b | lsb_c |
    |____________|_______|_______|_______|_______|
    |            |       |       |
    | 0xbf9ea11e | lsb_c |  pad  |
    |____________|_______|_______|

编译器这样做是为了生成比没有填充和内存对齐优化的代码更快地访问数据类型的代码。

你可以运行我的代码在这个link下demo program

结构只有一个成员,所以"packing"没有意义。成员之间没有填充,因为没有其他成员。

您可能想要打包数组,但这是不必要的,因为 uint32_t 是精确大小的类型。 (不需要存在,但对于缺少uint32_t的架构,这个问题是无关紧要的。)

所以如果你有一些偏心的 48 位架构,其中每个 "word" 由四个可寻址的 12 位 "bytes" 组成,你可能有一个编译器,其中 int是三个 "bytes" 长,四字节对齐,但你不会有 uint32_t,因为 int 类型是 36 位,而不是 32 位,并且(C99 §7.20.1.1 , 通过引用包含在 C++11):

The typedef name intN_t designates a signed integer type with width N, no padding bits, and a two’s complement representation.