您的位置:首页 > 职场人生

(摘抄笔记)从一道面试题看struct中的内存对齐

2016-10-03 09:40 246 查看

笔试题

struct T {
char a;
int *d;
int b;
int c:16;
double e;
};
T *p;


在64位操作系统下,下面选项正确的是()

A. sizeof(p) == 24

B. sizeof(*p) == 24

C .sizeof(p->a) == 1

D .sizeof(p->e) == 4

解析

32位编译器

char :1个字节

char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器) short int : 2个字节

int: 4个字节

unsigned int : 4个字节

float: 4个字节

double: 8个字节

long: 4个字节

long long: 8个字节

unsigned long: 4个字节

64位编译器

char :1个字节

char*(即指针变量): 8个字节

short int : 2个字节

int: 4个字节

unsigned int : 4个字节

float: 4个字节

double: 8个字节

long: 8个字节

long long: 8个字节

unsigned long: 8个字节

a占一个字节(注:地址为[0]),d作为64位指针占8个字节(注1:32位占四个字节,p也一样)(d的偏移量要为8的整数倍,所以d的地址为[8]-[15],而非[1]-[8],下同),b占了4个字节(注:地址为[16]-[19]),c指定为16为,占了两个字节(注:地址为[20,21]),e占8个字节,(同d的分析一样,e的地址应该为[24]-[31]),所以A的答案应该是8,B的答案是32,C正确,D的答案为8。

为什么要内存对齐

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低 字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈

举例:

假设我们同时声明两个变量

char a;

short b;

用&(取地址符号)观察变量a,b的地址的话,我们会发现(以16位CPU为例):

如果a的地址是0x0000,那么b的地址将会是0x0002。

那么就出现这样一个问题:0x0001这个地址没有被使用,那它干什么去了? 答案就是它确实没被使用。 因为CPU每次都是从以2字节(16位CPU)或是4字节(32位CPU)的整数倍的内存地址中读进数据的。如果变量b的地址是0x0001的话,那么CPU就需要先从0x0000中读取一个short,取它的高8位放入b的低8位,然后再从0x0002中读取下一个short,取它的低8位放入b的高8位中,这样的话,为了获得b的值,CPU需要进行了两次读操作。但是如果b的地址为0x0002,那么CPU只需一次读操作就可以获得b的值了。所以编译器为了优化代码,往往会根据变量的大小,将其指定到合适的位置,即称为内存对齐(对变量b做内存对齐,a、b之间的内存被浪费,a并未多占内存)。

内存对齐规则

这里面有四个概念值:

(1)结构体中每个成员分别按自己所占节数(自身对齐值)和PPB(指定的对齐值,32位机默认为4,可通过progma pack ()设置)两个字节数最小的那个对齐,这样可以最小化长度。如在32bit的机器上,int的大小为4,因此int存储的位置都是4的整数倍的位置开始存储。

(2)结构体中复杂类型(如结构,枚举)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,结构体数组的时候,可以最小化长度。如:在一个struct中包含另一个struct,内部struct应该以它的最大数据成员大小的整数倍开始存储。如 struct A 中包含 struct B, struct B 中包含数据成员 char, int, double,则 struct B 应该以sizeof(double)=8的整数倍为起始地址。

(3)结构体本身对齐方式是其成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。

有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。

1.有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的”存放起始地址%N=0”.

2.而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是 数据结构的起始地址。

3.结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体成员中最大的对齐参数的整数倍,结合下面例子理解)。

实例:

一:

/******        32位机器       ******/
struct A
{
char a;    //内存位置:  [0]
double b;   // 内存位置: [4]...[11]    ** 规则1 **
int c;    // 内存位置: [12]...[15]
};       

// 内存大小:sizeof(A) = (1+3) + 8 + (4+4) = 16,

struct B
{
int a,    // 内存位置: [0]...[3]
A b,       // 内存位置: [8]...[23]      ** 规则2 **
char c,   // 内存位置: [24]
};       

// 内存大小:sizeof(B) = 24


二:

/******        32位机器       ******/
include <iostream.h>
#pragma pack(8)
struct example1
{
short a;        //内存位置:  [0] ..[1]
long b;         //内存位置:  [4] ...[7]   ** 规则1 **
};
// 内存大小:sizeof(example1) = (2+2) + (4) = 8
struct example2
{
char c;        //内存位置:  [0]
example1 struct1;      //内存位置:  [4] ...[11]   ** 规则2 **
short e;       //内存位置:  [12] ...[13]
};
// 内存大小:sizeof(example2) = (1+3) + (8) + (2+2)= 16  ** 规则3 **
#pragma pack()
int main(int argc, char* argv[])
{
example2 struct2;
cout << sizeof(example1) << endl;
cout << sizeof(example2) << endl;
cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2) << endl;
return 0;
}


问程序的输入结果是什么?

答案是:

8

16

4

三:

设结构体如下定义:

struct A {
int a;         //内存位置:  [0] ..[3]
char b;        //内存位置:  [4]
short c; };    //内存位置:  [6] ..[7]
// 内存大小:sizeof(A)= 4 + (1+1) + 2 = 8


struct B {
char b;           //内存位置:  [0]
int a;            //内存位置:  [3] ..[7]
short c;          //内存位置:  [8] ..[9]
};
// 内存大小:sizeof(B) = (1+3) + 4 + (2 +2) = 12


#progma pack (2) /*指定按2字节对齐*/
struct C {
char b;         //内存位置:  [0]
int a;          //内存位置:  [2] ... [5]      ** 规则1 **
short c;        //内存位置:  [6] ... [7]
};
// 内存大小:sizeof(C) = (1+1)+ 4 + 2 =8


#progma pack (1) /*指定按1字节对齐*/
struct D {
char b;     //内存位置:  [0]
int a;      //内存位置:  [1] ... [4]
short c;    //内存位置:  [5] ... [6]
};
// 内存大小:sizeof(D) = 1 + 4 + 2 = 7


四:

设有一下说明和定义:

//32位机器
typedef union
{
long i;
int k[5];
char c;
}DATE;         //共同体变量公用空间,所占内存为其中最大变量类型,为20。
strucct data
{
int cat;    //   [0] ... [3]
DATE cow;   //   [4] ... [23]    /*不是[20] ... [39]*/
double dog; //   [24]... [31]
}too;
//sizeof(too) = 4 + 24 +8 = 32
DATE max;


则printf(“%d”,sizeof(struct data) + sizeof(max))执行的结果是?

结果是52.

#pragma pack ()
/取消指定对齐,恢复缺省对齐/

有了以上的解释,相信你对C语言的字节对齐概念应该有了清楚的认识了吧。在网络程序中,掌握这个概念可是很重要的喔,在不同平台之间(比如在Windows 和Linux之间)传递2进制流(比如结构体),那么在这两个平台间必须要定义相同的对齐方式,不然莫名其妙的出了一些错,可是很难排查的哦^_^

参考的文章:

http://blog.csdn.net/chengonghao/article/details/51861493

http://blog.csdn.net/kokodudu/article/details/11918219

http://blog.sina.com.cn/s/blog_59b189220100a49h.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: