C/C++题目--内存对齐

题目:什么是内存对齐?为什么要对齐?什么时候需要对齐?

【答案】

什么是内存对齐?

字节对齐(内存对齐)主要是根据编译器设定或者在特定系统环境下数据按一定的规则存取在内存中的位置。

为什么要对齐?

由于不同系统对数据存取的位置有着不同的要求,对齐规则也就会有些不同。在X86系统下,一般默认对齐为4的整数倍并作为自然对齐

CPU访问数据的效率问题(对一些系统要求效率高):

(1)若存取数据是整型变量,如果该变量置于0x0002,那么CPU存取它的值就要访问2次,即第一次一个0x0002到0x0003的short类型,第二次一个0x0004到x0005的short类型,两次结合一起得到整型变量。如果该变量置于0x0003,那么CPU存取它的值就要访问3次。

(2)不管是基本数据类型、对象还是数据结构等都离不开字节对齐规则。比如我们在函数内部定义一些基本数据类型在栈区,当您要打印他们各自的存储地址就会发现,像int,float,double类型,就会按偶地址来存储的,而对于char类型是按奇地址来存储的,这显然目的是为了存取数据的效率更快些。相比之下,在堆区里的数据就不一定了。

节省内存空间问题:

对于一些对CPU效率要求不高或者其他不同的要求,不必要采用默认字节对齐,而采用指定某字节对齐来减少存储空间。

什么时候需要对齐?

根据不同的编译器设置字节对齐来避免生成的代码出错。此外,对于一些存储效率的要求,我们不得不需要以时间效率来换取空间大小,同样的,以牺牲空间来换取时间的效率。

题目:对于结构体字节对齐,有哪些规则?

【答案】

总体上我们假设结构体起始位置为0x0000,N为设置的n字节对齐,则满足公式0x0000%N==0,来决定结构体成员存储的位置。

1.如果是采用默认对齐规则,需要执行以下的步骤:

(1)结构体的成员的自身对值N(数据类型占有的空间,比如在32位机器下int型为4字节,自身对值为4)需要与存储位置做出判断。若满足“起始位置%N=0”(除了结构体的第一个成员,N也要必须满足>=成员自身对齐值),则把该成员存放在该起始位置。若前一个成员与目前成员的存储位置有一定的距离,则把该距离作为填充空间。

(2)所有结构体的成员都分配完存储位置之后,则还要结构体本身也要进行对齐。结构体本身也要满足“(M+X)%S=0”来进行对齐,其中M+X是整个结构体占有的存储空间大小,M是所有结构体的成员的存储空间大小总和,X是为了满足S的整数倍的填补空间大小,S是就是结构体成员中自身对齐值最大的那个。

2.如果是采用#pragma pack(N)规则,需要执行以下的步骤:

(1)结构体成员的自身对齐值是偏移的量。若满足“起始位置%N=0”(这里N是指定对齐值,即#pragma pack(N)),则可以把该成员存储到该起始位置中,偏移量为该成员的自身对齐值(比如int型自身对齐值为4,占用4个字节的内存位置)。

(2)结构体自身对齐值为该结构体的所有成员自身对齐值中最大的值,但又因为结构体的有效对齐值(即指定对齐值)为N,所以“(M+X)%N=0”,M+X是该结构体存储的空间大小。

题目:以下结构体student的空间大小为多少?

struct student
{
char c1;
char c2;
};

【答案】

2

【解析】

楼主考虑出此题并非多余,主要是让大家结合上文的几道概念题进行加深理解。

由结果可知,c1在起始位置0x0000满足“0x0000%1=0”. (虽然实际上起始地址并非0x0000,但是在做题以及解决问题时,我们一定要把该值作为起始地址的参考点。)

c2的起始位置为0x0001,即满足了“0x0001%1=0”,在此可得公式为“起始地址%自身类型值=0”。

在该结构体中,结构体的有效对齐值为所有成员自身对齐值为最大的值,因成员只有char类型,所以结构体的有效对齐值为1。结构体的空间大小满足“(M+X)%S=0”,M为c1和c2的空间大小总和,X目前还是未知数,是c2存储空间之后的填充空间大小,S是结构体的有效对齐值,所以如果满足了公式的条件便可知道X的值,于是也就有了结构体的空间大小,即M+X。

如果结构体有double类型的,则结构体的有效对齐值为8字节,代码示例:

struct student
{
char c1;//起始地址:00 填充空间地址:01 02 03 04 05 06 07,共8个字节
double b;//起始地址:08 09 10 11 12 13 14 15 ,共8个字节
char c2;//起始地址:16 填充空间地址:17 18 19 20 21 22 23 ,共8个字节
}zhangsan;
//假设结构体空间大小为Sum:
Sum=所有成员的空间大小总和+填充空间大小
Sum=(8+8)+8=24

 

题目:为什么我的编译器在结构中留下了空洞?这导致空间浪费而且无法与外部数据文件进行“二进制”读写。能否关掉填充,或者控制结构域的对齐方式?

答案

当内存中的值合理对齐时,很多机器都能非常高效地访问。例如,在按字节寻址的机器中,2字节的short int型变量必须放在偶地址上,而4字节的long int型变量则必须存放在4的整数倍地址上。某些机器甚至根本就不能访问没有对齐的地址,因此必须要求所有的数据都正确地对齐。

假如你声明了这个结构:

struct
{
char c;
int I;
}

编译器通常都会在char型域和int型域之间留出一个没有命名也没有使用的空洞,以确保int型域正确对齐。(根据最保守的对齐要求,结构本身也是对齐的,因此第二个域可以根据第一个域的位置进行累进对齐。编译器保证它所分配的结构对齐,对malloc也是如此。)

编译器可能提供某种扩展用于控制结构的填充(可能是#pragma),但是没有标准的方法。

如果你真的那么在意被浪费的空间,可以把结构中的域按从大到小的顺序排列,以最大限度地降低填充的影响。数组成员应该根据它的元素类型大小而不是整个数组的大小进行排序。有时候,使用位域也可以更好地控制大小和对齐,但是这样也有它的缺点。

题目:如何确定域在结构中的字节偏移量?
答案

ANSI C在<stdddef.h>中定义了offsetof()宏,用offsetof(structs,f)可以计算出域f在结构s中的偏移量。如果出于某种原因,需要自己实现这个功能,可以使用下边这样的代码:

#define offsetof(type,f)((size_t) \

((char*)&((type*)0)->f-(char*)(type*)0))

这种实现不是100%的可移植;某些编译器可能会合法地拒绝接受。

(这复杂的定义需要一点解释。对类型转换后的空指针的减法是为了确保即时空指针的内部表示不是0的时候也能正确计算出偏移。转换成(char*)指针可以确保计算出的偏移是字节偏移。不可移植的地方在于,为了描述计算,需要假装0地址处有一个type型的结构。注意,由于并没有引用这个结构,所以出现非法访问的可能性很小。)

题目:什么叫做位域?

【答案】

位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。(答案摘自百度百科)

题目:一些结构声明中的这些冒号和数字式什么意思?

struct recond
{
char *name;
int refcount :4;
unsigned dirty:1;
};

【答案】

这是位域。数字表示该域用位计量的准确大小。使用位域可以在有很多二进制标志和其他小成员的结构中节省存储空间。也可以用于满足外部要求的存储布局。(位域在某些机器上从左到右分配,而在某些机器上从右到左分配,这使得位域在完成后一个任务时的成功率大打折扣。)

注意,用冒号指定二进制大小的方法只适用于结构(和联合)的成员。不能用这种方法来为任意变量指定大小。

题目:以下代码哪些地方不正确?

struct student
{
char c1:9;
int a:16;
char c2:8;
char c3:0;
};

【答案】

1.“char c1:9”错误,报错信息:位域类型对位数太小。由于c1是char类型的,所以c设置char的位域范围必须是0到8位。

2.“char c3:0;”错误,报错信息:已命名位域不能有零宽度。说明带有零宽度的类型不能命名,因此要去掉c3这个名称。

题目:以下代码输出结果为多少?

struct student
{
char c1;
int a:1;
char c2;
}zhangsan;

int main()
{
zhangsan.a=2;
cout<<zhangsan.a<<endl;
return 0;
}

【答案】

0

【解析】

      楼主原本以为该程序将会报错,因为域名只设置为1位,也就是只有两个值,以为输入值2是不够存储进去的,就算能够运行,也只要0和1的值。但让楼主万万没有想到的是,该程序居然能够通过编译。而且结果也让人意外,只有0和-1的值,值的范围为-1到0.

       从测试的众多数据可知,当设定的位域最高位置为1时,就转变为负数的最小数值.比如设置的位域为4,其值的范围为-8到7.也就是说无论输入多少,其结果都一定在位域值的范围内。假如设置位域为4,输入数据为19,其结果是3.分析一下:19的二进制为10011 结果的二进制为0011,所以,当位域最高位为1时,就会出现负数,当位域最高位为0时,就会按照设定位域的位数进行计算结果。这跟字符与整数进行强制转换的规则是一样的。

题目:X86系统在默认对齐的情况下,请输出以下代码的结果?

struct student
{
char c;
int score;
char name[10];
};
struct student zhangsan;

【答案】

20

【解析】

在X86系统下,编译器默认按照4的整数倍进行字节对齐。因此,对齐如下:

struct student
{
char c;//0x0000,0x000%1=0,共1个字节

//0x0001到0x003填充空间,共3个字节

int score;//0x0004到0x0007,0x0004%4=0,共4个字节

char name[10];//假设a为0x0008到0x00017地址,则(0x000a)%1=0,共10个字节

//由于结构体需要满足”(所有成员自身对齐值总和(1+3+4+10)+填充空间)%4=0”,则0x0018到0x0019需要填充空间为2个字节。
};

题目:X86系统下,请输出以下代码的结果?

#pragma pack(3)
struct student
{
char c;
int score;
char name[10];
};

【答案】

20

【解析】

因为#pragma pack(N),N的可取值为1,2,4,8,16,设置成其他值一律按照默认对齐规则处理。

题目:让编译器对这个结构体student设置1字节对齐规则,结构体存储空间是多少?

#pragma pack(1)
struct student
{
char c;//0x0000,共1个字节
int score;//0x0001到0x0004,共4个字节
char name[10];//0x0005到0x0014,共10个字节
};
#pragma pack()
struct student zhangsan;

【答案】

15

题目:输出结果是多少?

#pragma pack(1)
struct student
{
char b;
int a;
short c;
};

struct student2
{
char b;
int a;
short c;
};

int main()
{
cout<<sizeof(student)<<endl;
cout<<sizeof(student2)<<endl;
return 0;
}

【答案】

7 12

【解析】

#pragma pack(1)设置了以1字节对齐之后,并没有取消该设置,也就说没有恢复到默认对齐方式。所以结构体student以1字节对齐之后并没有取消该设置,student2继续以1字节对齐形式存储数据。

修改如下:在结构体student2定义之前,一定要添加该语句#pragma pack()恢复默认对齐,或者#pragma pack(n),n作为对齐系数重新设置成其他对齐值

 

猜你喜欢

转载自blog.csdn.net/chen1083376511/article/details/91884411