为什么结构的sizeof不等于每个成员的sizeof之和?

时间:2020-03-06 14:34:49  来源:igfitidea点击:

为什么'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语句来更改结构对齐设置。

如果我们隐式或者显式设置了结构的对齐方式,则可以这样做。对齐4的结构将始终是4字节的倍数,即使其成员的大小不是4字节的倍数也是如此。

同样,库可能会在x86下使用32位int进行编译,并且我们可能在64位进程中比较其组件,如果我们手动执行此操作,则会得到不同的结果。

打包和字节对齐,如此处的C常见问题解答所述:

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  |
+-------+-------+-------+-------+

这可能是由于字节对齐和填充所致,因此该结构在平台上显示为偶数个字节(或者字)。例如,在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字节(2x 32位)和1字节(2 + 6位)。上面的程序(在使用gcc的Linux上)将大小分别打印为4、8和4,并填充最后一个结构,以便它是一个单词(在我的32位平台上为4 x 8位字节)。

oneInt=4
twoInts=8
someBits=4

例如,如果我们希望结构在GCC中具有特定大小,请使用____ attribute __((packed))`。

在Windows上,将cl.exe编译器与/ Zp选项一起使用时,可以将对齐方式设置为一个字节。

通常,CPU会更轻松地访问4或者8的倍数的数据,具体取决于平台和编译器。

因此,这基本上是对齐的问题。

我们需要有充分的理由进行更改。

除了其他答案外,结构可以(但通常没有)具有虚函数,在这种情况下,结构的大小还将包括vtbl的空间。