在 C 结构中,为什么packed,aligned
似乎要做填充?
我必须使用 i2c 从远程设备读取一堆 MSB/LSB 字节。由于所有设备数据都是字节,因此我使用uint8_t
它来精确表示 8 位,即 1 个字节。
现在我需要确保结构是packed
(即结构中的元素之间没有“洞”),除非我不会读取远程设备中生成的数据。
然后,我添加了aligned
性能(即允许 CPU 以最少的内存访问读取结构)。
正如结构填充和打包所解释的,填充将结构成员与“自然”地址边界对齐,而打包可防止填充。
这个简单的测试让我很惊讶:
$ gcc --version
gcc (Debian 10.2.1-6) 10.2.1 20210110
$ cat dumb.c
#include <stdio.h>
#include <stdint.h> // uint8_t
struct struct1 {
uint8_t msb;
} __attribute__((packed));
struct struct2 {
uint8_t msb;
} __attribute__((packed,aligned(4)));
struct struct3 {
uint8_t msb;
uint8_t lsb;
} __attribute__((packed));
struct struct4 {
uint8_t msb;
uint8_t lsb;
} __attribute__((packed,aligned(4)));
int main(void) {
struct struct1 s1;
printf("sizeof(s1) %zu\n", sizeof(s1));
struct struct2 s2;
printf("sizeof(s2) %zu\n", sizeof(s2));
struct struct3 s3;
printf("sizeof(s3) %zu\n", sizeof(s3));
struct struct4 s4;
printf("sizeof(s4) %zu\n", sizeof(s4));
return 0;
}
$ gcc -o dumb dumb.c
$ ./dumb
sizeof(s1) 1
sizeof(s2) 4
sizeof(s3) 2
sizeof(s4) 4
aligned
似乎“填充了结构的末尾”:这是预期的结果吗?
- 我希望
s2
从 4 字节的倍数内存地址开始,但大小为 1。 - 我希望
s4
从 4 字节的倍数内存地址开始,但大小为 2。
我从远程设备读取这些字节,因此每个字节都使用 i2c 从设备传输到 PC:现在为了提高性能,最好packed
只避免“末尾的额外填充”,还是最好packed,aligned
每次传输时都传输和读取额外未使用的“末尾填充”字节?
aligned
声明上的属性意味着struct
结构的任何实例都必须从对齐的地址开始。这也意味着,如果您有一个结构数组,则该结构可能需要尾随填充,以便数组的每个成员都正确对齐。
虽然该
packed
属性将最小化结构中使用的任何填充,但与一起使用时,这种填充不一定能完全消除aligned
。例如如果你有这样的声明:
如果没有尾随填充,则
s2[1]
无法正确对齐。因为数据类型的对齐要求必须除以其大小。否则,您无法形成该类型的数组。如果您强制特定的对齐要求,则编译器可能需要调整大小(通过添加填充)。
一个可能更自然的方法是将 MSB/LSB 对分别读入单个
uint16_t
,确保没有内部填充。既然您这样描述这些对,我想您可能希望将这些对解释为 16 位数字,然后可以通过函数方便地实现ntohs()
。这很可能是过早的优化。任何因错位而导致的延迟都可能被您的 I/O 开销完全抵消。
或多或少。这是结构布局中成员之间填充的正常目的,但这不是此类填充的固有特性,也不是填充的唯一位置。
不完全是。打包控制指令的确切含义在不同的 C 实现中有所不同,但您使用的是 GCC,其定义是:
此外,
...在这种情况下,
所以不,它根本不直接与填充有关。强制将(非位域)成员的对齐要求设置为 1 个字节确实会导致它们在布局时没有任何前导填充,但这并不能说明后面是否有任何填充。
是的,正如前面讨论过的。而且这并不与 的定义相冲突
packed
。再次,对齐要求为 4 的类型的大小将是 4 的倍数,因此您的期望是不正确的。但是,我认为 GCC 允许您对齐单个变量,因此如果您真的想要您描述的内容,那么您应该能够通过将属性
aligned
从类型移动到相应的变量来实现这些特定变量:但是,我再次认为你对此过于担心。通过 i2c 接口移动字节将耗费大量的时间。
谁说您会将任何东西读入任何结构填充?你不应该。如果您一次读取一对字节,那么您无论如何都不会这样做。如果您在单个操作中读取多个字节对的序列,那么您就不能进行填充。但同样,你似乎把事情弄得比需要的困难得多。我会跳过结构并使用
uint16_t
字节对的类型。特别是如果你一起读取许多字节对,在这种情况下你会使用uint16_t[]
。假设你想将每对解释为一个 16 位数字,ntohs()
在读取之后对每个对进行后处理,然后再执行任何其他操作。如果结果不够快,则对其进行分析以确定瓶颈在哪里,然后进行改进。