C/C++中的数据结构对齐,#pragma pack() 和 __attribute__

2023-11-17

C/C++中的数据结构对齐

总览

数据结构对齐是指在计算机内存中排列和访问数据的方式。它包含三个独立但相关的问题:数据对齐(data alignment),数据结构填充( data structure padding)和打包(packing)。

当数据自然对齐时,现代计算机硬件中的CPU最有效地执行对内存的读写操作,这通常意味着数据的内存地址是数据大小的倍数

  1. 读/写总是从word_size的倍数的地址开始的。
  2. 读/写的长度总是word_size的倍数。 例如,在32位体系结构中,如果数据存储在4个连续字节中并且第一个字节位于4字节边界上,则可以对齐数据。

数据对齐是根据元素的自然对齐来进行的。为了确保自然对齐,可能需要在结构元素之间或结构的最后一个元素之后插入一些填充。例如,在32位机器上,一个包含16位值和32位值的数据结构可以在16位值和32位值之间有16位的填充,以使32位值在32位边界对齐。或者,我们可以将结构打包(packing),省略填充,这可能会导致较慢的访问速度,但只使用了四分之三的内存。

尽管数据结构对齐是所有现代计算机的一个基本问题,但许多计算机语言和计算机语言实现都会自动处理数据对齐。Fortran、Ada、 PL/I、Pascal、某些C和C++实现、D、Rust、C#和汇编语言,允许至少在部分范围内控制数据结构填充,这在某些特殊情况下可能是有用的

数据对齐(alignment)

当一个内存地址a是n的倍数(其中 n = 2 k n=2^k n=2k)时,可以说是n字节对齐的。在这种情况下,一个字节是最小的内存访问单位,即每个内存地址指定一个不同的字节。一个n字节对齐地址,再用二进制表示时将至少有 l o g 2 ( n ) log_2(n) log2(n)个最低有效零。

而b位对齐,实际上就是另一种说法,指 b / 8 b/8 b/8字节对齐的地址(例如,64位对齐的是8字节对齐的)。

当被访问的数据长度为n个字节并且基准地址是n个字节对齐的时候就可以说一个内存访问是对齐的。当一个内存访问没有被对齐时,它被称为错位(misaligned)。请注意,根据定义,字节内存访问总是被对齐的

如果一个内存指针指的是n字节长的原始数据并且只允许它包含n字节对齐的地址那么这个指针就被称为对齐的。否则,它就被称作不对齐的(unaligned)。当(且仅当)集合中的每个原始数据是对齐的,指向数据集合(数据结构或数组)的内存指针才是对齐的。

请注意,上面的定义假定每个原始数据的长度是 2 k 2^k 2k。如果不是这种情况(如x86的80位浮点),上下文会影响数据是否被认为是对齐的条件。

数据结构可以存储在堆栈中,在栈中的静态尺寸被称为 有界(bounded,在堆中的动态尺寸被称为 无界(unbounded

典型对齐方式

结构的典型对齐方式:

  • 数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。

  • 联合 :按其包含的长度最大的数据类型对齐。

  • 结构体结构体中每个数据类型都要对齐

结构字节对齐的原则主要有:

  1. 数据类型自身的对齐值:char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节 …(操作系统不同可能由偏差),在最后会有介绍。

  2. 结构体或类的自身对齐值其成员中自身对齐值最大的那个值。(结构体的每一个成员相对结构体首地址的偏移量,应该其参数类型占字节数的整数倍,如果不满足则补足前面的字节使其满足),例如:

    typedef struct node2 {
        int a;
        char b;
        short c;
    } S2;
    

    补位结构为==(4 — 1 + 1(补) — 2)==,因为最后一个short 2 2 2字节,但其前一个char 1 1 1位,不补位其地址较结构体首地址偏移 5 ≠ 2 ∗ N 5 \ne 2*{\rm{N}} 5=2N,所以前面char 1 1 1位,首地址偏移 6 = 2 ∗ N 6=2*N 6=2N ,总大小为 8 8 8字节,具体地址如下:

    image-20230426121248566

    这其实就是数据结构填充的补齐方式,详细例子在后面的填充中会说明。

  3. 指定对齐值#pragma pack (value)时的指定对齐值value。

  4. 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值}。

不对齐存在的问题

CPU一次访问内存一个内存字。只要内存字大小至少与计算机支持的最大基本数据类型一样大,对齐访问将始终访问一个内存字。然而,对于未对齐的数据访问可能就不是这样了。

如果一个数据的高位和低位字节不在同一个内存字中,计算机必须将该数据的访问分成多个内存访问。 这需要复杂的电路来生成内存访问并协调它们。为了处理内存字位于不同的内存页的情况,处理器必须在执行指令之前验证两个内存页是否都存在,或者在任何内存访问过程中能够处理TLB缺失或页面故障。

内存地址不对齐,会引起什么样的问题呢?用一个例子来说明:

地址未对齐的情况

假定在一台32位机器上,有一个整型变量i的地址是34 ,那i存储在内存的34、35、36、37地址;

为了把这个变量从内存读进CPU,由于计算机从内存读取数据的天性(第一点,读/写总是从word_size的倍数的地址开始的),需要两次读取(第一次从32开始读32 33 34 35,第二次从36开始读36 37 38 39),然后把第一次读取的后两个字节(34 35)抽取出来,把第二次读取的前面两个字节(36 37)抽取出来拼到一起组成变量i:

一个int变量为4bytes,即32位,从CPU一次可以读取的内存块长度来看,本可以一次读完;但是因为这个变量的内存块地址没有对齐,将导致本来一个read指令就能完成的读取操作,需要两次read外加其它复杂的抽取拼接计算,从而大大地降低了性能

一些处理器设计有意避免引入这种复杂性,在不对齐的内存访问时会产生替代行为。 例如,在ARMv6 ISA之前的一些ARM架构实现需要所有多字节的加载和存储指令强制对齐内存访问。根据执行哪个具体的指令,尝试未对齐访问的结果可能是将违规地址的最低有效位舍入为对齐访问(有时附加其他警告),或者抛出MMU异常(如果有MMU硬件),或者无声地产生其他潜在不可预测的结果。ARMv6和更新的架构在许多情况下支持不对齐访问,但并非全部情况。

当单个内存字被访问时,操作是原子性的,即整个内存字被一次性读或写,其他设备必须等到读或写操作完成后才能访问它。这对于对多个内存字的非对齐访问来说可能不是正确的,例如,第一个字由一个设备读取,然后另一设备写入两个字,然后再由第一设备读取第二个字。这种情况读取的值既不是原始值也不是更新的值。虽然这种故障很少,但却很难识别。

解决对齐:数据结构填充(padding)

尽管编译器(或解释器)通常将单个数据项分配到对齐的边界上,但数据结构通常具有不同对齐要求的成员。为了保持适当的对齐,翻译器通常插入额外的未命名数据成员,以便每个成员都得到适当的对齐。此外,整个数据结构可能会用最后一个未命名成员填充。这使得结构数组的每个成员都可以得到适当的对齐。

仅当结构成员后面紧跟具有较大对齐要求的成员或位于结构末尾时,才插入填充。通过改变结构中成员的顺序,可以改变维护对齐所需的填充量。例如,如果成员按降序排列要求进行排序,则需要最少的填充。所需的最少填充量始终小于结构中最大的对齐要求。计算所需最大填充量更为复杂,但始终小于所有成员对齐要求之和减去最不对齐的一半结构成员对齐要求之和的两倍。

尽管 C 和 C++ 不允许编译器重新排序结构成员以节省空间,但其他语言可能允许。大多数 C 和 C++ 编译器也可以指定将结构体成员“打包”到特定的对齐级别,例如 “pack(2)”表示将大于一个字节的数据成员对齐到两个字节边界,使得任何填充成员最多为一个字节长。同样,在 PL/I 中,可以声明结构体为 UNALIGNED 以除去除位串外的所有填充。

这种 "打包 " 结构的 一个用途是节约内存。例如,一个包含一个字节(如char)和一个四字节整数(如uint32_t)的结构将需要三个额外的字节填充。一个由此类结构组成的大数组如果被打包,将减少37.5%的内存,尽管访问每个结构可能需要更长时间。这种妥协可以被认为是一种空间-时间权衡的形式。

尽管使用 "打包 "结构最常被用来节省内存空间,但它也可以被用来 格式化数据结构,以便使用标准协议进行传输。然而,在这种用法中,还必须注意确保结构成员的值是以协议要求的字节数(通常是网络字节数)来存储的,这可能与主机本身使用的字节数不同。

计算填充

下面的公式提供了对准数据结构的开始所需的填充字节数(其中mod是模除运算符):
 padding  = (  align  − (  offset   m o d   align  ) )   m o d   a l i g n  aligned  =  offset  +  padding  =  offset  + ( (  align  − (  offset   m o d   align  ) )   m o d   a l i g n ) \begin{aligned} \text { padding } & =(\text { align }-(\text { offset } \ mod \ \text{ align })) \ mod \text \ { align } \\ \text { aligned } & =\text { offset }+ \text { padding } \\ & =\text { offset }+((\text { align }-(\text { offset } \ mod \ \text{ align })) \ mod \text \ { align }) \end{aligned}  padding  aligned =( align ( offset  mod  align )) mod align= offset + padding = offset +(( align ( offset  mod  align )) mod align)
例如,对于一个地址偏移是0x59d的4字节对齐结构体,该结构体将从0x5a0开始存储并添加3字节的填充,因为0x5a0是4的倍数。计算过程如下:
 padding  = ( 4 − ( 0x59d  m o d   4 ) )   m o d   4 = ( 4 − 1 )   m o d   4 = 3  aligned  = 0x59d + 3 = 0x5a0 \begin{aligned} \text { padding } &= (4 - (\text{0x59d} \ mod \ 4)) \ mod \ 4 = (4 - 1) \ mod \ 4 = 3 \\ \text { aligned } &= \text{0x59d} + 3 = \text{0x5a0} \end{aligned}  padding  aligned =(4(0x59d mod 4)) mod 4=(41) mod 4=3=0x59d+3=0x5a0
反之,当地址偏移量offset已经和对齐字节数align相等时,第二个*(align - (offset mod align)) mod align*中的模除将返回0,因此原始值将保持不变。

因为对齐操作根据定义是按照2的幂次方,所以模除运算可以简化为布尔和位运算。

下面的公式可以产生正确的值(其中&是位与,~是位非)—— 但前提是地址偏移量是无符号的,或者系统使用二进制补码运算:

 padding  = (  align  − (  offset &  (  align  − 1 ) ) ) & ( align ⁡ − 1 ) = −  offset &  (  align  − 1 )  aligned  = (  offset  + (  align  − 1 ) ) & ∼ (  align  − 1 ) = (  offset  + (  align  − 1 ) ) & − a l i g n \begin{aligned} \text { padding } & =(\text { align }-(\text { offset \& }(\text { align }-1))) \&(\operatorname{align}-1) \\ & =- \text { offset \& }(\text { align }-1) \\ \text { aligned } & =(\text { offset }+(\text { align }-1)) \& \sim(\text { align }-1) \\ & =(\text { offset }+(\text { align }-1)) \&-a l i g n \end{aligned}  padding  aligned =( align ( offset & ( align 1)))&(align1)= offset & ( align 1)=( offset +( align 1))&( align 1)=( offset +( align 1))&align

数据对齐的编译器处理

编译器尝试以防止数据未对齐的方式分配数据。对于简单的数据类型,编译器将分配是数据类型的大小(以字节为单位)的倍数的地址。 例如,编译器将地址分配给类型为 long 且是 4 的倍数的变量,并将地址的最底部 2 位设置为零。

编译器还以自然对齐结构的每一个元素的方式填充结构。 来看看下面代码示例中的结构 struct x_

struct x_
{
   char a;     // 1 byte
   int b;      // 4 bytes
   short c;    // 2 bytes
   char d;     // 1 byte
} bar[3];

编译器填充此结构以自动强制实施对齐方式。

下面的代码示例展示了编译器如何将填充的结构置于内存中:

// Shows the actual memory layout
struct x_
{
   char a;            // 1 byte
   char _pad0[3];     // padding to put 'b' on 4-byte boundary
   int b;            // 4 bytes
   short c;          // 2 bytes
   char d;           // 1 byte
   char _pad1[1];    // padding to make sizeof(x_) multiple of 4
} bar[3];

两个声明都将 sizeof(struct x_) 作为 12 个字节返回。

第二个声明包括两个填充元素:

  1. char _pad0[3]对齐 4 字节边界上的 int b 成员,为了int b地址偏移为 4 ∗ ( s i z e o f ( i n t ) ) 4*(sizeof(int)) 4(sizeof(int)),补3字节。
  2. char _pad1[1]对齐 4 字节边界上结构 struct _x bar[3] 的数组元素,为了数组中下一个struct _x bar地址偏移为 4 ∗ N 4*N 4N,补1字节。

填充以允许自然访问的方式对齐 bar[3] 的元素。

下面的代码示例展示了 bar[3] 的数组布局:

adr offset   	element
------   		-------
0x0000   		char a;         // bar[0]
0x0001   		char pad0[3];
0x0004   		int b;
0x0008   		short c;
0x000a   		char d;
0x000b   		char _pad1[1];

0x000c   		char a;         // bar[1]
0x000d   		char _pad0[3];
0x0010   		int b;
0x0014   		short c;
0x0016   		char d;
0x0017   		char _pad1[1];

0x0018   		char a;         // bar[2]
0x0019   		char _pad0[3];
0x001c   		int b;
0x0020   		short c;
0x0022   		char d;
0x0023   		char _pad1[1];

结构体填充举例

如果有如下八个结构体:

typedef struct node1 {
} S1;
typedef struct node2 {
    int a;
    char b;
    short c;
} S2;
typedef struct node3 {
    char a;
    int b;
    short c;
} S3;
typedef struct node4 {
    int a;
    short b;
    long c; 
} S4;
typedef struct node5 {
    char a;
    S1 b;
    short c;
} S5;
typedef struct node6 {
    char a;
    S2 b;
    int c;
} S6;
typedef struct node7 {
    char a;
    S2 b;
    double d;
    int c;
} S7;
typedef struct node8 {
    char a;
    S2 b;
    char* c;
} S8;

int main() {
    S1 xx;
    printf("start:\t 0x%p \n", &xx);
    printf("end:\t 0x%p \n", &xx+1);
    return 0;
}

按照之前的公式,在未指定对齐的情况下,各个结构体占用的内存如下:

node1

node1为一个空结构体,在C中空结构体的大小为0字节,在C++中空结构体的大小为1字节。

typedef struct node1 {	// C:0, C++:1
} S1;

image-20230426121141645

node2

node2的内存结构:(4 — 1 + 1(补) — 2),总大小为8字节(结构体的每一个成员相对结构体首地址的偏移量应该是对其参数的整数倍)。

typedef struct node2 {
    			// 系数	当前地址偏移		下一系数	padding				最终空间
    int a;		// 4	4			%		1		=0 => 补0 			4
    char b;		// 1	(1+4)		%		2		=1 => 补2-1=1		1 + 1(补)	
    short c;	// 2	(2+2+4)		%		4		=0 => 补0			2
    			// 最后一个元素的下一系数参考本结构体最大元素的系数
} S2;

image-20230426121248566

node3

node3的内存结构:(1 +3(补) — 4 — 2 +2(补)),总大小为12字节(结构体的每一个成员相对结构体首地址的偏移量应该是对其参数的整数倍)。

typedef struct node3 {
    			// 系数	当前地址偏移		下一系数	padding				最终空间
    char a;		// 1	1			%		4		=1 => 补4-1=3 		1 +3(补)
    int b;		// 4	(4+4)		%		2		=0 => 补0 			4
    short c;	// 2	(2+4+4)		%		4		=2 => 补4-2=2 		2 +2(补)
    			// 最后一个元素的下一系数参考本结构体最大元素的系数
} S3;

image-20230426121822281

node4

node4的内存结构:(4 — 2 + 2(补)—4),总大小为12字节。

typedef struct node4 {
    			// 系数	当前地址偏移		下一系数	padding				最终空间
    int a;		// 4	4			%		2		=0 => 补0 			4
    short b;	// 2	(2+4)		%		4		=2 => 补4-2=2 		2 + 2(补)
    long c; 	// 4	(4+4+4)		%		4		=0 => 补0 			4
    			// 最后一个元素的下一系数参考本结构体最大元素的系数
} S4;

image-20230426121712484

node5

node5的内存结构:(1 — 1 — 2),总大小为4字节。

typedef struct node5 {
    			// 系数	当前地址偏移		下一系数	padding			最终空间
    char a;		// 1	1			%		0		->0 => 补0 			1
    S1 b;		// 0	(0+1)		%		2		=1 => 补1 			1
    short c;	// 2	(2+1+1)		%		4		=0 => 补0 			2
    			// 最后一个元素的下一系数参考本结构体最大元素的系数
} S5;

image-20230426121937421

node6

node6的内存结构:(1 + 3(补) — 8 — 4),总大小为16字节,注意结构体变量的对齐参数的计算。

这里char补3的原因是: S2结构体内基本数据类型占空间最大的是4字节int类型 ,所以要补足4个字节。

typedef struct node6 {
    			// 系数	当前地址偏移		下一系数	padding				最终空间
    char a;		// 1	1			%		4		=1 => 补4-1=3 		1 + 3(补)
    S2 b;		// 4	(8+4)		%		4		=0 => 补0 			8
    int c;		// 4	(4+8+4)		%		4		=0 => 补0 			4
    			// 最后一个元素的下一系数参考本结构体最大元素的系数
} S6;

image-20230426122016567

node7

node7的内存结构:(1 + 3(补)— 8 + 4(补) — 8 — 4 + 4(补)),总大小为32字节。

typedef struct node7 {
    			// 系数	当前地址偏移		下一系数	padding				最终空间
    char a;		// 1	1			%		4		= 1 => 补4-1=3 		1 + 3(补)
    S2 b;		// 4	(8+4)		%		8		= 4 => 补8-4=4		8 + 4(补)
    double d;	// 8	(8+12+4)	%		4		= 0 => 补0 			8
    int c;		// 4	(4+8+12+4)	%		8		= 4 => 补8-4=4 		4 + 4(补)
    			// 最后一个元素的下一系数参考本结构体最大元素的系数
} S7;

image-20230426122304199

node8

node8的内存结构:(1 + 3(补) — 8 + 4(补)— 8),所有数据类型指针在64位系统都占8字节,总大小为24字节。

typedef struct node8 {
    			// 系数	当前地址偏移		下一系数	padding				最终空间
    char a;		// 1	1			%		4		= 1 => 补4-1=3 		1 + 3(补)
    S2 b;		// 4	(8+4)		%		8		= 4 => 补8-4=4 		8 + 4(补)
    char* c;	// 8	(8+12+4)	%		8		= 0 => 补0 			8
    			// 最后一个元素的下一系数参考本结构体最大元素的系数
} S8;

image-20230426122052089

C语言结构体在x86上的典型对齐方式

数据结构成员在内存中是按顺序存储的,因此,在下面的结构中,成员Data1总是在Data2之前;而Data2总是在Data3之前:

struct MyData
{
    short Data1;
    short Data2;
    short Data3;
};

如果 "short "类型存储在2字节的内存中,那么上面描述的数据结构的每个成员都将是2字节对齐的。数据1在偏移0,数据2在偏移2,数据3在偏移4。这个结构体的大小将是6个字节。

结构中每个成员的类型通常有一个默认的对齐方式,也就是说,除非程序员另有要求,否则它将在一个预先确定的边界上对齐。以下典型的对齐方式对微软(Visual C++)、Borland/CodeGear(C++Builder)、Digital Mars(DMC)和GNU(GCC)的编译器在为32位x86编译时有效:

数据类型 对齐字节数
char(1字节) 1字节对齐
short(2字节) 2字节对齐
int(4字节) 4字节对齐
long(4字节) 4字节对齐
float(4字节) 4字节对齐
double(8字节) Windows上是8字节对齐的,在Linux上是4字节对齐的(用-malign-double编译时选项是8字节)
long long(8字节) 4字节对齐
long double((C++Builder和DMC为10个字节,Visual C++为8个字节,GCC为12个字节)) C++Builder上将是8字节对齐,DMC为2字节对齐,Visual C++为8字节对齐,GCC为4字节对齐。
指针(4字节)
(例如:char*, int*)
4字节对齐
long(8字节) 8字节对齐
double(8字节) 8字节对齐
long long(8字节) 8字节对齐
long double(在Visual C++中是8个字节,在GCC中是16个字节) 在Visual C++中是8字节对齐的,在GCC中是16字节对齐的。
指针(8字节) 8字节对齐

有些数据类型则取决于实现方式。

强制指定对齐大小

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:

使用伪指令#pragma pack (n),C编译器将**按照n个字节对齐**。

使用伪指令#pragma pack ()取消自定义字节对齐方式

gcc里还可以使用__attribute__关键字来声明数据类型的对齐方式,优先级高于#pragma预编译指令。

__attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

attribute ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

__attribute__方式

struct stu{
   char sex;
   int length;
   char name[10];
}__attribute__ ((aligned (1))); 
struct stu my_stu;

此时,sizeof(my_stu)可以得到大小为15。上面的定义等同于:

struct stu{
    char sex;
    int length;
    char name[10];
}__attribute__ ((packed)); 
struct stu my_stu;
  • 当aligned作用于**变量时,其作用是告诉编译器为变量分配内存的时候,要分配在指定对其的内存上,作用于变量之上不会改变变量的大小**。

    例如:int a attribute((aligned(16))),该变量a的内存起始地址为16的倍数。

  • 当aligned作用于**类型时,其作用是告诉编译器该类型声明的所有变量都要分配在指定对齐的内存上。当该属性作用于结构体声明时可能会改变结构体的大小**。

测试的时候发现 __attribute__ ((aligned (1)))用法不适用于minGW ,使用第二种,效果如下:

struct node6 {
    char a;
    S2 b;
    int c;
}__attribute__ ((packed)) ;
typedef struct node6 S6;

可以发现与之前的声明相比,内存分配发生了变化,变量分配在了1对齐的内存上:

image-20230426185643455

pragma方式

声明#pragma可以设置对齐参数的数值,缺省是8字节:

#pragma pack(n) 				//作用:C编译器将按照n个字节对齐。
#pragma pack()               	//作用:取消自定义字节对齐方式。
#pragma pack(push,1)     		//作用:是指把原来对齐方式设置压栈,并设新的对齐方式设置为一个字节对齐
#pragma pack(pop)            	//作用:恢复对齐状态
#pragma pack(push)				//保存对齐状态
#pragma pack(4)					//设定为4字节对齐 相当于 #pragma  pack (push,4)  

例如之前的node6,使用强制指定对齐的pack方式,实际上就是指定系数均为2:

# pragma pack(2)
typedef struct node6 {
    			// 系数	当前地址偏移		下一系数	padding				最终空间
    char a;		// 2	1			%		2		=1 => 补2-1=1 		1 + 1(补)
    S2 b;		// 2	(8+2)		%		2		=0 => 补0 			8
    int c;		// 2	(4+8+2)		%		2		=0 => 补0 			4
    			// 最后一个元素的下一系数参考本结构体最大元素的系数
} S6;
#pragma pack()

可以看到运行结果符合对齐要求,占用空间从之前的16字节缩小到了14字节:

image-20230426184840790

aligned和pack的主要区别

  1. pack作用于结构体或类的定义,而**aligned既可以作用于结构体或类的定义,也可以作用于变量的声明**。
  2. pack的作用是改变结构体或类中成员变量的布局规则而aligned只是建议编译器对指定变量或指定类型的变量分配内存时的规则
  3. pack可以压缩变量所占内存的空间
  4. align可以指定变量在内存的对其规则,而pack不可以。
  5. 若某一个结构体的默认pack为n,pack指定的对齐规则m大于n,则该pack忽略若aligned指定的对齐规则s大于n,则此时结构体的大小一定为s的整数倍
  6. aligned和pack指定规则时都必须为2的n次幂。

拓展:64 位数据模型

在 32 位程序中,指针和整数等数据类型通常具有相同的长度。但在 64 位机器上不一定如此。因此,在C及其后代(如C++和Objective-C)等编程语言中混合数据类型可能适用于 32 位实现,但不适用于 64 位实现。

在 64 位机器上的许多 C 和 C 派生语言的编程环境中,int变量仍然是 32 位宽,但long intpointer是 64 位宽。这些被描述为具有LP64 数据模型,它是==“Long, Pointer, 64”== 的缩写。其他模型有 ILP64数据模型,其中所有三种数据类型都是64位宽,甚至还有SILP64模型,其中short也是64位宽。然而,在大多数情况下,所需的修改相对较小且直接,许多编写良好的程序可以简单地针对新环境重新编译而无需更改。另一种选择是LLP64模型,它通过将intlong保留为 32 位来保持与 32 位代码的兼容性。LL指的是long long integer类型,在所有平台上至少是64位,包括32位环境。

也有使用ILP32数据模型的 64 位处理器的系统,添加了 64 位 long long 整数;这也用于许多具有 32 位处理器的平台。该模型以更小的地址空间为代价减少了代码大小和包含指针的数据结构的大小,对于某些嵌入式系统来说是一个不错的选择。对于 x86 和 ARM 等指令集,其中 64 位版本的指令集比 32 位版本的指令集具有更多的寄存器,它提供了对额外寄存器的访问而没有空间损失。它在 64 位 RISC 机器中很常见,在 x86 中作为x32 ABI 进行了探索,并且最近被用于Apple Watch Series 4和 5。

Data model short (integer) int long (integer) long long pointers, size_t Sample operating systems
ILP32 16 32 32 64 32 x32 and arm64ilp32 ABIs on Linux systems; MIPS N32 ABI.
LLP64 16 32 32 64 64 Microsoft Windows (x86-64 and IA-64) using Visual C++; and MinGW
LP64 16 32 64 64 64 Most Unix and Unix-like systems, e.g., Solaris, Linux, BSD, macOS. Windows when using Cygwin; z/OS
ILP64 16 64 64 64 64 HAL Computer Systems port of Solaris to the SPARC64
SILP64 64 64 64 64 64 Classic UNICOS[46][47] (versus UNICOS/mp, etc.)

今天许多64位平台使用LP64模型(包括Solaris、AIX、HP-UX、Linux、macOS、BSD和IBM z/OS)。微软Windows使用LLP64模型LP64模型的缺点是,将long存储到int中会被截断。另一方面,在LP64中,将一个指针转换为一个长字符串会 “有效”。在LLP64模型中,情况恰恰相反。这些问题并不影响完全符合标准的代码,但代码的编写往往隐含着对数据类型宽度的假设。C代码在将指针转换为整数对象时,应该选择(u)intptr_t而不是long

参考文献

1:Data structure alignment - Wikipedia

2:数据结构对齐 - 维基百科,自由的百科全书

3:64-bit computing - Wikipedia

4:Data structure alignment (数据结构对齐 / 内存对齐)_Jeff_的博客-CSDN博客

5:对齐方式 | Microsoft Learn


如有疑问或错误,欢迎和我私信交流指正。
版权所有,未经授权,请勿转载!
Copyright © 2023 by Mr.Idleman. All rights reserved.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C/C++中的数据结构对齐,#pragma pack() 和 __attribute__ 的相关文章

  • 将集合绑定到自定义控件属性

    我没有运气尝试将数据集合绑定到我的自定义控件的属性 我已经实现了该控件的字符串属性的机制 在此处提供了一些帮助 并期望集合类型同样简单 但是我无法让它再次工作 这是我的自定义控件视图
  • FileStream 构造函数和默认缓冲区大小

    我们有一个使用 NET 4 用 C 编写的日志记录类 我想添加一个构造函数参数 该参数可以选择设置文件选项 WriteThrough http msdn microsoft com en us library system io fileo
  • 为什么在创建矩阵类时使用向量不好?

    对于我的矩阵类 我做了 template
  • 在 Xamarin 中隐藏软键盘

    如何隐藏软键盘以便在聚焦时显示Entry在 Xamarin forms 便携式表单项目中 我假设我们必须为此编写特定于平台的渲染器 但以下内容不起作用 我创建自己的条目子类 public class MyExtendedEntry Entr
  • EF Core 通过完全替换断开集合导航属性的更新

    使用 EF Core 5 0 我有一个 SPA 页面 可以加载Group实体及其集合Employee来自 API 的实体 var groupToUpdate await context Groups Include g gt g Emplo
  • 防止 boost::asio::io_context 在空轮询调用时停止

    此代码调用发布的句柄 boost asio io context ioc boost asio post ioc std cout lt lt lol lt lt std endl ioc poll 而这并没有 boost asio io
  • 指向特征矩阵的指针数组

    我在代码中使用 Eigen 的 MatrixXd 矩阵 在某个时刻我需要一个 3D 矩阵 由于 Eigen 没有三维矩阵类型 因为它仅针对线性代数进行了优化 因此我创建了一个 MatrixXd 类型的指针数组 Eigen MatrixXd
  • fprintf() 线程安全吗?

    我正在为野人就餐问题的某些变量编写一个 C 解决方案 现在 我创建线程 每个线程都将 FILE 获取到同一个调试文件 在线程内我正在使用 fprintf 进行一些打印 打印的语句不受任何类型的互斥锁等保护 我没有在调试文件中观察到任何交错行
  • 如何获取 QTableView 的标题列表?

    我有一个QTableView我的对话框中的对象 我需要访问该表的水平标题并将它们放入QStringList object 尽管进行了大量搜索 但我在 Qt 文档中找不到如何获取此标头列表 编辑 我发现的最接近的地方是this https w
  • 如何在标准 WPF ListView 中启用 UI 虚拟化

    我正在使用 NET 4 5 VS2012 并且我有一个 ListView 看起来像这样
  • 无法在内存位置找到异常源:cudaError_enum

    我正在尝试确定 Microsoft C 异常的来源 test fft exe 中 0x770ab9bc 处的第一次机会异常 Microsoft C 异常 内存位置 0x016cf234 处的 cudaError enum 我的构建环境是 I
  • 如何通过 JsonConvert.DeserializeObject 在动态 JSON 中使用 null 条件运算符

    我正在使用 Newtonsoft 反序列化已知的 JSON 对象并从中检索一些值 如果存在 关键在于对象结构可能会不断变化 因此我使用动态来遍历结构并检索值 由于对象结构不断变化 我使用 null 条件运算符来遍历 JSON 代码看起来像这
  • 将标量添加到特征矩阵(向量)

    我刚刚开始使用 Eigen 库 无法理解如何向所有矩阵成员添加标量值 假设我有一个矩阵 Eigen Matrix3Xf mtx Eigen Matrix3Xf Ones 3 4 mtx mtx 1 main cxx 104 13 error
  • cout 和字符串连接

    我刚刚复习了我的 C 我尝试这样做 include
  • 跨多个域的 ASP.NET 会话

    是否有合适的 NET 解决方案来在多个域上提供持久服务器会话 即 如果该网站的用户在 www site1 com 下登录 他们也将在 www site2 com 下登录 安全是我们正在开发的程序的一个问题 Thanks 它是否需要在会话中
  • 每个数据库多个/单个 *.edmx 文件

    我有一个通过 ADO net 数据服务与数据库交互的项目 数据库很大 近 150 个具有依赖关系的表 该项目几年前开始 当时使用的是数据集 现在我们正在转向实体模型关系 由于我们添加了更多需要使用的表 该模型正在不断增长 这是管理这一切的正
  • 如何在 DropDownList 中保留空格 - ASP.net MVC Razor 视图

    我在视图中通过以下方式绑定我的模型 问题是我的项目文本是格式化文本 单词之间有空格 如下所示 123 First 234 00 123 AnotherItem 234 00 123 Second 234 00 我想保留此项目文本中的空格 即
  • C++0x中disable_if在哪里?

    Boost 两者都有enable if and disable if 但 C 0x 似乎缺少后者 为什么它被排除在外 C 0x 中是否有元编程工具允许我构建disable if按照enable if 哦 我刚刚注意到std enable i
  • 使我的 COM 程序集调用异步

    我刚刚 赢得 了在当前工作中维护用 C 编码的遗留库的特权 这个dll 公开使用 Uniface 构建的大型遗留系统的方法 除了调用 COM 对象之外别无选择 充当此遗留系统与另一个系统的 API 之间的链接 在某些情况下 使用 WinFo
  • 从 JavaScript 中的 OnClientClick 事件中阻止 C# 中的 asp:Button OnClick 事件?

    我有一个asp Button在我的网页上 它调用 JavaScript 函数和代码隐藏方法 后者进行调用以导航到另一个页面 在 JavaScript 函数中 我正在检查条件 如果不满足这个条件 我想中止导航 以便OnClick方法未被调用

随机推荐

  • Unity 编辑器-创建模板脚本,并自动绑定属性,添加点击事件

    当使用框架开发时 Prefab挂载的很多脚本都有固定的格式 从Unity的基础模板创建cs文件 再修改到应有的模板 会浪费一些时间 尤其是有大量的不同界面时 每个都改一遍 浪费时间不说 还有可能遗漏或错改 写个脚本创建指定的模板代替C 基础
  • 基于Spark的电商用户行为实时分析可视化系统(Flask-SocketIO)

    基于Spark的电商用户行为实时分析可视化系统 Flask SocketIO 项目简介 该项目已上线蓝桥课程 有需要的可凭邀请码 UB5mdLbl 学习哦 有优惠 课程地址 https www lanqiao cn courses 2629
  • 拉普拉斯时域卷积定理_如何证明频域卷积定理

    展开全部 设抄 IF表示傅立叶逆变换 则 因此有袭 故频域卷积定2113理5261得证 4102 扩展资料 频域卷积定理 频域卷积定理表明两信号1653在时域的乘积对应于这两个信号傅立叶变换的卷积除以2 卷积定理揭示了时间域与频率域的对应关
  • nacos注册中心/配置中心的使用

    Nacos下载 https github com alibaba nacos releases Nacos启动 此处为了演示方便 下载的是 Windows版本 nacos server 2 2 2 zip 进入 nacos server 2
  • response.setCharacterEncoding(charset) 报错

    eclipse tomcat服务启动运行项目 代码 response setCharacterEncoding charset 标红 我自己百度了很多 有一个说法是最靠谱的 HttpServletResponse存在于servlet api
  • JQ了解

    jQuery 一 jQuery的了解 1 定义 jQuery是一个兼容多浏览器的JavaScript框架 可以使用户方便地处理HTML 事件 实现动画效果 并且为网站提供方便的Ajax交互 2 作用 JS操作DOM的一个库 特点 轻量级 出
  • Maven构建与管理项目(二)

    Maven构建与管理项目 二 Maven核心概念 Maven坐标 什么是坐标 在平面几何中坐标 x y 可以标识平面中唯一的一点 Maven坐标主要组成 groupId 组织标识 包名 artifactId 项目名称 version 项目的
  • ST外设使用出错,一般排查步骤

    FMC 通信有误排查过程 1 STM32 官方设计资源 https www stmcu com cn STM32中文官网 gt 设计资源 gt 实战经验 2 Cube 库当中的官方例程 使用 everything 搜索 FMC 关键词 从而
  • 新路子!chatGPT+Python爬虫接私单怎么玩?

    就在这两天 关于ChatGPT的疾呼突然在社交平台上刷屏 很多人发现自己的号已经不在了 用户们感到前所未有的惶恐 已经有不少公司把 chatGPT引入工作流 未来已来 AI智能时代真的来了 普通人如何在智能时代谋求发展 这里提供一个思路 c
  • C++异常

    全文目录 概念 异常的抛出 在函数调用链中异常栈展开匹配原则 异常的重新抛出 异常安全 异常规范 C 标准库的异常体系 异常的优缺点 概念 C语言处理异常的方式 终止程序 返回错误码 很多系统的库函数就是使用这中方式 C 异常 异常是一种处
  • iphone原彩显示对眼睛好吗_iphonex原彩显示有必要开吗

    配置来看 iphoneX更好也2113更贵 iphone8注重5261实用 iphoneX注重装X 有钱就买iphone X 预算不足买iphone 8 4102iphone8和iphoneX的区别 1653外观设计 iPhone 8 Pl
  • 工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计

    鸿鹄工程项目管理系统 Spring Cloud Spring Boot Mybatis Vue ElementUI 前后端分离构建工程项目管理系统 1 项目背景 一 随着公司的快速发展 企业人员和经营规模不断壮大 为了提高工程管理效率 减轻
  • caffe代码阅读7:LayerRegistry的实现细节-2016.3.18

    一 LayerRegistry的作用简介 LayerResistry的功能很简单 就是将类和对应的字符串类型放入到一个map当中去 以便灵活调用 主要就是注册类的功能 二 LayerRegistry类的详细介绍 1 构造函数和析构函数 构造
  • c-tree数据库(c-treeACE)(7):开发篇之一

    C tree提供了很多种的API 我们主要使用C 的 即c treeDB C API 主要的参考手册就是Faircom公司网站上提供的 c treeDB C API Developer s Guide 学习开发的一个最佳途径当时看看tuto
  • Python 中的自动点击器——2 种简单易行的方法

    在本教程中 我们将了解Python 中的自动答题器 我们将首先了解它的含义以及如何在 Python 中实现它 那么 事不宜迟 让我们进入正题 Auto Clicker是一种 Python 软件 允许用户以较短的时间间隔连续点击鼠标 它由用户
  • Mysql中关于NULL值的处理

    一 Mysql空值介绍 MySQL认为任何和NULL值做比较的表达式的值都为NULL 包括select null null和select null null 在对统计索引列不重复值的数量时如何对待NULL值 MySQL专门提供了一个inno
  • React 练习项目,仿简书博客写作平台

    Introduction 技术栈 react redux react router express Nginx 练习点 redux 连接 react router 路由跳转 scss 样式书写 容器组件与展示组件的设计 express 脚手
  • 【100%通过率 】【华为OD机试c++】人数最多的站点【2023 Q1

    华为OD机试 题目列表 2023Q1 点这里 2023华为OD机试 刷题指南 点这里 题目描述 关注公园园区提供小火车单向通行 从园区站点编号最小到最大通行如1 2 3 4 1 然后供员工在各个办公园区穿梭 通过对公司N个员工调研统计到每个
  • cloudflare解析域名+CDN

    cloudflare解析域名 CloudFlare 是一家全球知名的 CDN 服务商 并且提供了免费的 CDN 套餐 还不限流量 所以我们完全不需要花一分钱就能使用它的 CDN 服务 接下来我就说明如何注册并使用 CloudFlare 1
  • C/C++中的数据结构对齐,#pragma pack() 和 __attribute__

    C C 中的数据结构对齐 总览 数据结构对齐是指在计算机内存中排列和访问数据的方式 它包含三个独立但相关的问题 数据对齐 data alignment 数据结构填充 data structure padding 和打包 packing 当数