C 语言中的结构化内存布局

我有 C # 背景,对于像 C 这样的低级语言来说,我还是个新手。

在 C # 中,struct的内存默认由编译器布局。编译器可以重新排列数据字段或隐式地在字段之间填充其他位。因此,我必须指定一些特殊的属性来覆盖这种行为以获得准确的布局。

AFAIK,C 默认情况下不重新排序或对齐 struct的内存布局。但是,我听说有一个很难找到的小例外。

什么是 C 的内存布局行为? 什么应该重新排序/对齐而不是?

135259 次浏览

You can start by reading the data structure alignment wikipedia article to get a better understanding of data alignment.

From the wikipedia article:

Data alignment means putting the data at a memory offset equal to some multiple of the word size, which increases the system's performance due to the way the CPU handles memory. To align the data, it may be necessary to insert some meaningless bytes between the end of the last data structure and the start of the next, which is data structure padding.

From 6.54.8 Structure-Packing Pragmas of the GCC documentation:

For compatibility with Microsoft Windows compilers, GCC supports a set of #pragma directives which change the maximum alignment of members of structures (other than zero-width bitfields), unions, and classes subsequently defined. The n value below always is required to be a small power of two and specifies the new alignment in bytes.

  1. #pragma pack(n) simply sets the new alignment.
  2. #pragma pack() sets the alignment to the one that was in effect when compilation started (see also command line option -fpack-struct[=] see Code Gen Options).
  3. #pragma pack(push[,n]) pushes the current alignment setting on an internal stack and then optionally sets the new alignment.
  4. #pragma pack(pop) restores the alignment setting to the one saved at the top of the internal stack (and removes that stack entry). Note that #pragma pack([n]) does not influence this internal stack; thus it is possible to have #pragma pack(push) followed by multiple #pragma pack(n) instances and finalized by a single #pragma pack(pop).

Some targets, e.g. i386 and powerpc, support the ms_struct #pragma which lays out a structure as the documented __attribute__ ((ms_struct)).

  1. #pragma ms_struct on turns on the layout for structures declared.
  2. #pragma ms_struct off turns off the layout for structures declared.
  3. #pragma ms_struct reset goes back to the default layout.

In C, the compiler is allowed to dictate some alignment for every primitive type. Typically the alignment is the size of the type. But it's entirely implementation-specific.

Padding bytes are introduced so every object is properly aligned. Reordering is not allowed.

Possibly every remotely modern compiler implements #pragma pack which allows control over padding and leaves it to the programmer to comply with the ABI. (It is strictly nonstandard, though.)

From C99 §6.7.2.1:

12 Each non-bit-field member of a structure or union object is aligned in an implementation- defined manner appropriate to its type.

13 Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.

It's implementation-specific, but in practice the rule (in the absence of #pragma pack or the like) is:

  • Struct members are stored in the order they are declared. (This is required by the C99 standard, as mentioned here earlier.)
  • If necessary, padding is added between struct members, to ensure that the latter one uses the correct alignment.
  • Each primitive type T requires an alignment of sizeof(T) bytes.

So, given the following struct:

struct ST
{
char ch1;
short s;
char ch2;
long long ll;
int i;
};
  • ch1 is at offset 0
  • a padding byte is inserted to align...
  • s at offset 2
  • ch2 is at offset 4, immediately after s
  • 3 padding bytes are inserted to align...
  • ll at offset 8
  • i is at offset 16, right after ll
  • 4 padding bytes are added at the end so that the overall struct is a multiple of 8 bytes. I checked this on a 64-bit system: 32-bit systems may allow structs to have 4-byte alignment.

So sizeof(ST) is 24.

It can be reduced to 16 bytes by rearranging the members to avoid padding:

struct ST
{
long long ll; // @ 0
int i;        // @ 8
short s;      // @ 12
char ch1;     // @ 14
char ch2;     // @ 15
} ST;