sizeof
运算符为什么返回的结构体大小比结构体成员变量的总和要大?
sizeof
运算符为什么返回的结构体大小比结构体成员变量的总和要大?
这是因为添加了填充以满足对齐约束条件。 数据结构对齐 影响程序的性能和正确性:
SIGBUS
)。以下是使用x86处理器的典型设置 (所有使用32和64位模式) 的示例:
struct X
{
short s; /* 2 bytes */
/* 2 padding bytes */
int i; /* 4 bytes */
char c; /* 1 byte */
/* 3 padding bytes */
};
struct Y
{
int i; /* 4 bytes */
char c; /* 1 byte */
/* 1 padding byte */
short s; /* 2 bytes */
};
struct Z
{
int i; /* 4 bytes */
short s; /* 2 bytes */
char c; /* 1 byte */
/* 1 padding byte */
};
const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */
通过按对齐方式排序成员(在基本类型中,按大小排序就足够了,例如上面示例中的结构体Z
),可以使结构体的大小最小化。
重要提示:C和C++标准都指出结构体对齐是实现定义的。因此,每个编译器可能会选择不同的数据对齐方式,导致不同和不兼容的数据布局。因此,在处理将由不同编译器使用的库时,了解编译器如何对齐数据非常重要。一些编译器具有命令行设置和/或特殊的#pragma
语句以更改结构体对齐设置。
打包和字节对齐,就像在C语言FAQ这里所描述的:
It's for alignment. Many processors can't access 2- and 4-byte quantities (e.g. ints and long ints) if they're crammed in every-which-way.
Suppose you have this structure:
struct { char a[3]; short int b; long int c; char d[3]; };
Now, you might think that it ought to be possible to pack this structure into memory like this:
+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+
But it's much, much easier on the processor if the compiler arranges it like this:
+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+
In the packed version, notice how it's at least a little bit hard for you and me to see how the b and c fields wrap around? In a nutshell, it's hard for the processor, too. Therefore, most compilers will pad the structure (as if with extra, invisible fields) like this:
+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+
s
的此类型的结构变量,则&s.a == &s
,&s.d == &s + 12
(给出答案中显示的对齐方式)。仅当数组具有可变大小(例如,a
声明为char a[]
而不是char a[3]
)时,才会存储指针,但是元素必须存储在其他地方。 - kbolino__attribute__((packed))
。
在 Windows 上,当使用 cl.exe 编译器时,可以使用 /Zp 选项 将对齐方式设置为 1 字节。
通常,CPU 更容易访问与平台和编译器相关的多个 4(或 8)字节的数据。
因此,基本上这是一个对齐的问题。
你需要有充分的理由来改变它。
#pragma pack(1)
- 它被 MSVC、gcc 和 clang 支持,这使得你的代码更具可移植性。 - mvp这可能是由于字节对齐和填充导致的,使得结构在您的平台上输出为偶数个字节(或单词)。例如,在Linux上使用C语言,以下3个结构:
#include "stdio.h"
struct oneInt {
int x;
};
struct twoInts {
int x;
int y;
};
struct someBits {
int x:2;
int y:6;
};
int main (int argc, char** argv) {
printf("oneInt=%zu\n",sizeof(struct oneInt));
printf("twoInts=%zu\n",sizeof(struct twoInts));
printf("someBits=%zu\n",sizeof(struct someBits));
return 0;
}
有三个成员变量的大小(以字节为单位)分别为4字节(32位),8字节(2×32位)和1字节(2+6位)。在Linux上使用gcc编译的上述程序将这些大小打印为4、8和4 - 最后一个结构被填充,使其成为一个单词(在我的32位平台上,一个单词是4个8位字节)。
oneInt=4
twoInts=8
someBits=4
:2
和:6
实际上在这种情况下指定的是2位和6位,而不是完整的32位整数。someBits.x只有2位,因此只能存储4个可能的值:00、01、10和11(1、2、3和4)。这有意义吗?这是一篇关于该功能的文章:https://www.geeksforgeeks.org/bit-fields-c/ - Kyle Burton参见:
对于Microsoft Visual C:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
和GCC声称与Microsoft的编译器兼容:
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Structure_002dPacking-Pragmas.html
除了之前的回答外,请注意,无论如何打包,C++中没有成员顺序保证。编译器可能(并且肯定会)将虚表指针和基础结构的成员添加到结构中。即使标准没有保证虚拟表的存在(虚拟机制的实现未指定),因此可以得出这样的保证是不可能的。
我非常确定C中成员顺序是有保证的,但在编写跨平台或跨编译器程序时,我不会依赖它。
结构体的大小大于其各部分之和,这是因为所谓的打包。特定处理器有首选的数据大小。大多数现代处理器的首选大小为32位(4字节)。当数据在此类边界上时,访问内存比跨越该大小边界的数据更有效率。
例如,考虑以下简单结构:
struct myStruct
{
int a;
char b;
int c;
} data;
如果机器是32位机器,且数据对齐在32位边界上,我们会看到一个即时的问题(假设没有结构对齐)。在这个例子中,让我们假设结构数据从地址1024(0x400)开始(注意最低的2位为零,因此数据对齐在32位边界上)。对于data.a的访问将正常工作,因为它以0x400的边界开始。对于data.b的访问也将正常工作,因为它位于0x404的地址——另一个32位边界。但是,未对齐的结构将把data.c放在地址0x405处。data.c的4个字节分别位于0x405、0x406、0x407和0x408。在32位机器上,系统将在一个内存周期内读取data.c,但只能获得其中的3个字节(第4个字节在下一个边界上)。因此,系统必须进行第二次内存访问才能获取第4个字节。#pragma pack 1
struct MyStruct
{
int a;
char b;
int c;
short d;
} myData;
I = sizeof(myData);
现在我应该已经得到了长度为11。如果没有使用编译器指示,根据编译器的默认对齐方式,它可以是从11到14(对于某些系统甚至可以达到32)。
#pragma pack
。如果成员按照它们的默认对齐方式进行分配,我通常会说该结构未被打包。 - Keith ThompsonC99 N1256标准草案
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 sizeof运算符:
3 当应用于具有结构或联合类型的操作数时,结果是这种对象中所有字节的总数,包括内部和尾随填充。
6.7.2.1 结构体和联合体说明符:
13 ... 结构体对象内部可能存在未命名的填充,但不在其开头。
并且:
15 结构体或联合体的末尾可能存在未命名的填充。
新的C99 灵活数组成员特性 (struct S {int is[];};
) 也可能会影响填充:
16 作为一个特殊情况,具有多个命名成员的结构体的最后一个元素可能具有不完整的数组类型; 这称为灵活数组成员。 在大多数情况下,灵活数组成员将被忽略。 特别地,结构体的大小就像灵活数组成员被省略一样,除了它可能具有比省略意味着更多的尾随填充。
Annex J 可移植性问题 再次强调:
以下内容是未指定的: ...
- 在结构体或联合体中存储值时的填充字节的值(6.2.6.1)
C++11 N3337标准草案
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3 Sizeof:
2 当应用于类时,结果是该类对象中的字节数,包括为将该类型的对象放置在数组中而需要的任何填充。
9.2 类成员:
使用reinterpret_cast适当转换后,指向标准布局结构对象的指针指向其初始成员(或如果该成员是位域,则指向其中所在的单元),反之亦然。[ 注意:因此可能在标准布局结构对象内部存在未命名的填充,但不在其开头,以实现适当的对齐。-注释]
我只知道足够的C++,可以理解这个注释 :-)
如果您已经隐式或显式设置了结构的对齐方式,它就可以这样做。即使其成员的大小不是4的倍数,对齐为4的结构体始终将是4字节的倍数。
此外,某个库可能是在使用32位整型的x86下编译的,而您可能在64位进程中比较其组件,如果您手动完成此操作,则会得到不同的结果。
C语言在内存中的结构元素的位置方面留给编译器一些自由度:
sizeof
运算符的结果包括内存空洞的大小,但不包括C/C++中可用的灵活数组的大小。C语言为程序员提供了一些保证结构元素排列方式的方法:
与元素对齐相关的问题:
对齐的工作方式:
p.s 更详细的信息可在此处找到:"Samuel P.Harbison, Guy L.Steele C A Reference, (5.6.2 - 5.6.7)"
这个想法是为了提高速度和缓存效果,操作数应该从与它们自然大小对齐的地址中读取。为了实现这一点,编译器会填充结构成员,以便下一个成员或下一个结构体能够对齐。
struct pixel {
unsigned char red; // 0
unsigned char green; // 1
unsigned int alpha; // 4 (gotta skip to an aligned offset)
unsigned char blue; // 8 (then skip 9 10 11)
};
// next offset: 12
x86架构一直能够获取不对齐的地址。然而,它速度较慢,当不对齐的地址跨越两个不同的缓存行时,会驱逐两个缓存行,而对齐访问只会驱逐一个。
有些架构实际上必须在不对齐读写时进行陷阱处理,早期版本的ARM架构(演变成今天所有移动CPU的那个)...嗯,它们实际上只返回错误数据。(忽略低位)
最后,请注意缓存行可以是任意大的,编译器不会尝试猜测它们或进行空间与速度的权衡。相反,对齐决策是ABI的一部分,并表示最小对齐方式,最终将均匀填满缓存行。
简而言之:对齐很重要。
alignof(int)
是程序中任何可能存在的int
的最小对齐方式,包括在struct
内部。每种类型的最小对齐方式由实现定义,这并不总是它的大小(例如,在某些32位平台上,alignof(long long) == 4
)。这就是ISO C允许实现适应不同CPU要求的方式。通常的选择是将类型对齐到其大小或寄存器宽度(如果更小),但alignof(int)==1
是您可以考虑用于x86的一种情况。 - Peter Cordesalignof()
的最大值。(packed
结构体是一种扩展,它改变了规则;它们允许您创建一个未对齐的 int
,因此只能通过结构体安全地访问,而不能取指向它的指针。请参见 为什么对 mmap'ed 内存的未对齐访问有时会在 AMD64 上导致 segfault? 以获取关于未对齐指针的示例,即使在编译非 SIMD 加载不需要对齐的 x86 时,也是 C 未定义行为。) - Peter Cordes