以下内容转载自
https://blog.csdn.net/lime1991/article/details/44536343
1.什么是对齐?为什么要对齐?
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n )
封装类是在内存中将其一个成员直接放在另一个后面,这可能表示对齐部分或全部成员的边界可以小于默认对齐目标体系结构。 pack
提供了数据声明级别的控制。 这与编译器选项 /Zp 不同,该选项只提供模块级别控制。 在杂注出现之后,pack
在第一个 struct
、union
或 class
声明处生效。pack
对定义没有影响。 在没有参数的情况下调用 pack
会将 n
设置为编译器选项 /Zp 中设置的值。 如果未设置编译器选项,则默认值为 8。
如果更改某个结构的对齐方式,它可能不会使用像内存中一样多的空间,但您可能会发现性能降低或者甚至是因未对齐访问而遇到硬件产生的异常。 您可以使用 SetErrorMode 修改此异常行为。
show (可选)
显示封装对齐的当前字节值。 该值由警告消息显示。
push (可选)
将当前封装对齐值推送到内部编译器堆栈上,并将当前封装对齐值设置为 n
。 如果未指定 n
,则将推送当前封装对齐值。
pop (可选)
从内部编译器堆栈的顶部移除记录。 如果没有用 n
指定 pop,则与堆栈顶部的生成的记录关联的封装值是新的封装对齐值。 如果指定了 n
(例如 #pragma pack(pop, 16)
),n
将成为新的封装对齐值。 如果使用 identifier
(例如 #pragma pack(pop, r1)
)进行弹出,则堆栈上的所有记录都将弹出,直到找到包含 identifier
的记录。 该记录将会弹出,与堆栈顶部的生成的记录关联的封装值是新的封装对齐值。 如果使用在堆栈上的任何记录中均未发现的 identifier
进行弹出,则会忽略 pop。
identifier
(可选)
当与 push 一起使用时,为内部编译器堆栈上的记录指定名称。 当与 pop 一起使用时,从内部堆栈中弹出记录,直到移除 identifier
;如果未在内部堆栈上找到 identifier
,则不会弹出任何内容。
n
(可选)
指定要用于封装的值(以字节为单位)。 如果没有为模块设置编译器选项 /Zp,n
的默认值为 8。 有效值为 1、2、4、8 和 16。 成员将在作为 n
的倍数或成员的大小的倍数的边界(以较小者为准)上对齐。
#pragma pack(pop,
identifier``,
n``)
是不确定的。
有关如何修改对齐方式的详细信息,请参阅以下主题:
结构对齐示例(特定于 x64)
警告 请注意,在 Visual Studio 2015 和更高版本可使用标准的 alignas 和 alignof 运算符,这 不同于
__alignof
和declspec( align )
,它们是跨编译器可移植的。 C++ 标准不能解决封装,因此仍必须使用pack
(或其他编译器上相应的扩展)来指定小于目标体系结构字体大小的对齐方式。
以下示例演示如何使用 pack
杂注更改结构的对齐方式。
// pragma_directives_pack.cpp #include <stddef.h> #include <stdio.h> struct S { int i; // size 4 short j; // size 2 double k; // size 8 }; #pragma pack(2) struct T { int i; short j; double k; }; int main() { printf("%zu ", offsetof(S, i)); printf("%zu ", offsetof(S, j)); printf("%zu\n", offsetof(S, k)); printf("%zu ", offsetof(T, i)); printf("%zu ", offsetof(T, j)); printf("%zu\n", offsetof(T, k)); }
0 4 8 0 4 6
以下示例演示如何使用 push、pop 和 show 语法。
// pragma_directives_pack_2.cpp // compile with: /W1 /c #pragma pack() // n defaults to 8; equivalent to /Zp8 #pragma pack(show) // C4810 #pragma pack(4) // n = 4 #pragma pack(show) // C4810 #pragma pack(push, r1, 16) // n = 16, pushed to stack #pragma pack(show) // C4810 #pragma pack(pop, r1, 2) // n = 2 , stack popped #pragma pack(show) // C4810
3.1 基本数据类型所占内存大小
3.2 Test1
-
-
struct Test1
-
{
-
char c;
-
short sh;
-
int a;
-
float f;
-
int *p;
-
char *s;
-
double d;
-
};
总共占28Bytes。 c的偏移量为0,占1个Byte。sh占2个Byte,它的对齐模数是2(2<4,取小者),存放起始地址应该是2的整数倍,因此c后填充1个空字符,sh的起始地址是2。a占4个Byte,对齐模数是4,因此接在sh后存放即可,偏移量为4。f占4个字节,对齐模数是4,存放地址是4的整数倍,起始地址是8。p,s的起始地址分别是12,16。d占8个字节,对齐模数是4(4<8),d从偏移地址为20处存放。存放后结构体占28个字节,是4的整数倍不用补空字符。
-
struct Test2
-
{
-
char c;
-
double d;
-
int a;
-
short sh;
-
float f;
-
int *p;
-
char *s;
-
};
将Test1个变量的顺序换一下位置,结构体Test2占用内存32Byte,可见写结构体时,将各个变量按所占内存从小到大排列所占结构体所占内存较小。
3.3关于静态变量static
静态变量的存放位置与结构体实例的存储地址无关,是单独存放在静态数据区的,因此用siezof计算其大小时没有将静态成员所占的空间计算进来。
-
-
struct Test3
-
{
-
char c;
-
short sh;
-
int a;
-
float f;
-
int *p;
-
char *s;
-
double d;
-
static double sb;
-
static int sn;
-
};
sizeof(Test3)=28
3.4关于类
空类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。
(一)类内部的成员变量:
- 普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
- static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。
(二)类内部的成员函数:
- 普通函数:不占用内存。
- 虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的
-
-
class cBase{};
-
-
class CBase1
-
{
-
private:
-
char c;
-
short sh;
-
int a;
-
public:
-
void fOut(){ cout << "hello" << endl; }
-
};
3.4.2 包含虚函数的类
-
-
class CBase2
-
{
-
private:
-
char c;
-
short sh;
-
int a;
-
public:
-
virtual void fOut(){ cout << "hello" << endl; }
-
};
3.4.3 子类
-
-
class CBase2
-
{
-
private:
-
char c;
-
short sh;
-
int a;
-
public:
-
virtual void fOut(){ cout << "virtual 1" << endl; }
-
};
-
class cDerive : public CBase
-
{
-
private :
-
int n;
-
public:
-
virtual void fPut(){ cout << "virtual 2"; }
-
};
sizeof(cDerive)= sizeof(cBase)+sizeof(int n) = 16