如何在不使用编译指示的情况下禁用 C 中的结构填充?
6 回答
没有标准的方法来做到这一点。该标准规定,填充可以由实现自行决定。来自 C996.7.2.1 Structure and union specifiers
第 12 段:
结构或联合对象的每个非位域成员都以适合其类型的实现定义的方式对齐。
话虽如此,您可以尝试几件事。
第一个你已经打折了,#pragma
用来试图说服编译器不要打包。无论如何,这不是便携式的。也没有任何其他特定于实现的方法,但您应该检查它们,因为如果您确实需要此功能,可能需要这样做。
第二个是按照从大到小的顺序对字段进行排序,例如所有long long
类型后跟long
一个,然后是所有类型int
,short
最后是char
类型。这通常会起作用,因为通常较大的类型具有更严格的对齐要求。再次,不便携。
第三,您可以将类型定义为char
数组并转换地址以确保没有填充。但请记住,如果变量没有正确对齐,一些架构会变慢,还有一些架构会惨遭失败(例如引发 BUS 错误和终止进程)。
最后一个有一些进一步的解释。假设您有一个按以下顺序包含字段的结构:
char C; // one byte
int I; // two bytes
long L; // four bytes
使用填充,您最终可能会得到以下字节:
CxxxIIxxLLLL
x
填充在哪里。
但是,如果您将结构定义为:
typedef struct { char c[7]; } myType;
myType n;
你得到:
CCCCCCC
然后,您可以执行以下操作:
int *pInt = &(n.c[1]);
int *pLng = &(n.c[3]);
int myInt = *pInt;
int myLong = *pLng;
为你带来:
CIILLLL
同样,不幸的是,不便携。
所有这些“解决方案”都依赖于您对编译器和底层数据类型的深入了解。
除了 pragma pack 之类的编译器选项之外,您不能,填充在 C 标准中。
您始终可以尝试通过在结构中最后声明最小类型来减少填充,如下所示:
struct _foo {
int a; /* No padding between a & b */
short b;
} foo;
struct _bar {
short b; /* 2 bytes of padding between a & b */
int a;
} bar;
具有 4 字节边界的实现的注意事项
在某些架构上,如果要求处理未对齐的数据,CPU 本身会反对。为了解决这个问题,编译器可以生成多个对齐的读取或写入指令,移位和拆分或合并各个位。您可以合理地期望它比对齐的数据处理慢 5 或 10 倍。但是,该标准并不要求编译器准备好这样做……考虑到性能成本,它的需求还不够。支持显式控制填充的编译器提供了它们自己的 pragma,因为 pragma 是为非标准功能保留的。
如果您必须使用未填充的数据,请考虑编写您自己的访问例程。您可能想尝试使用需要较少对齐的类型(例如使用 char/int8_t),但仍有可能例如结构的大小将四舍五入为 4 的倍数,这会阻碍紧密包装结构,在这种情况下您需要对整个内存区域实现您自己的访问。
要么让编译器进行填充,要么告诉它不要使用#pragma,要么只使用一些字节,如 char 数组,然后自己构建所有数据(移动和添加字节)。这确实效率低下,但您将完全控制字节的布局。我有时会手动准备网络数据包,但在大多数情况下这是一个坏主意,即使它是标准的。
如果您真的想要没有填充的结构:使用仅由 8 位字节组成的结构或类定义 short、int、long 等的替换数据类型。然后使用替换数据类型组成更高级别的结构。
C++ 的运算符重载非常方便,但是您可以在 C 中使用结构而不是类来实现相同的效果。下面的强制转换和赋值实现假设 CPU 可以处理未对齐的 32 位整数,但其他实现可以适应更严格的 CPU。
这是示例代码:
#include <stdint.h>
#include <stdio.h>
class packable_int { public:
int8_t b[4];
operator int32_t () const { return *(int32_t*) b; }
void operator = ( int32_t n ) { *(int32_t*) b = n; }
};
struct SA {
int8_t c;
int32_t n;
} sa;
struct SB {
int8_t c;
packable_int n;
} sb;
int main () {
printf ( "sizeof sa %d\n", sizeof sa ); // sizeof sa 8
printf ( "sizeof sb %d\n", sizeof sb ); // sizeof sb 5
return 0;
}
我们可以使用以下任何一种方法禁用 c 程序中的结构填充。
->__attribute__((packed))
在结构定义后面使用。例如。
struct node {
char x;
short y;
int z;
} __attribute__((packed));
->-fpack-struct
在编译 c 代码时使用标志。例如。
$ gcc -fpack-struct -o tmp tmp.c
希望这可以帮助。谢谢。