或许有不少读者会问,字节对齐有必要拿出来独自写一篇博客嘛?我觉得是很有必要,可是它却是被许多人所忽视的一个要点。那么咱们运用字节对齐的效果和原因是什么呢?因为硬件渠道之间对存储空间的处理上是有很大不同的,一些渠道对某些特定类型的数据只能从某些特定地址开端存取,如一般有些架构的CPU要求在编程时有必要确保字节对齐,不然拜访一个没有进行字节对齐的变量的时分会产生过错。而有些渠道或许没有这种状况,可是一般的状况是假如咱们编程的时分不依照合适其渠道要求对数据寄存进行对齐,会在存取功率上带来丢失。比方有些渠道每次读都是从偶地址开端,如咱们操作一个int型数据,假如寄存在偶地址开端的当地,那么一个读周期就能够读出,而假如寄存在奇地址开端的当地,就或许会需求2个读周期,两个周期读取出来的字节咱们还要对它们进行凹凸字节的凑集才干得到该int型数据,然后使得咱们的读取功率较低,这也从旁边面反映出了一个问题,便是咱们许多时分是在献身空间来节省时间的。
或许看了上面的解说你仍是不太了解,那咱们再来看一次什么是字节对齐呢? 咱们现在的核算机中内存空间都是依照字节来进行区分的,从理论上来讲的话好像对任何类型的变量的拜访能够从任何地址开端,可是值得注意的便是,实际状况下在拜访特定变量的时分经常在特定的内存地址拜访,然后就需求各种类型的数据依照必定的规则在空间上摆放,而不是次序的一个接一个的排放,这便是对齐。
依照预先的方案组织,这次应该是写《C言语的那些小秘密之链表(三)》的,可是我发现假如直接开端解说linux内核链表的话,或许有些当地假如咱们不在此做一个恰当的解说的话,有的读者看起来或许难以了解,所以就把字节对齐挑出来另写一篇博客,我在此尽或许的解说完关于字节对齐的内容,期望我的解说对你有所协助。
在此之前咱们不得不提的一个操作符便是sizeof,其效果便是回来一个目标或许类型所占的内存字节数。咱们为什么不在此称之为sizeof()函数呢?看看下面一段代码:
[html] view plaincopy#include
void print()
{
printf("hello world!\n");
return ;
}
void main()
{
printf("%d\n",sizeof(print()));
return ;
}
这段代码在linux环境下我选用gcc编译是没有任何问题的,关于void类型,其长度为1,可是假如咱们在vc6下面运转的话话就会呈现illegal sizeof operand过错,所以咱们称之为操作符愈加的精确些,既然是操作符,那么咱们来看看它的几种运用方法:
1、sizeof( object ); // sizeof( 目标 );
2、 sizeof( type_name ); // sizeof( 类型 );
3、sizeof object; // sizeof 目标; 一般这种写法咱们在代码中都不会运用,所以很少见到。
下面来看段代码加深下形象:
[html] view plaincopy#include
void main()
{
int i;
printf("sizeof(i):\t%d\n",sizeof(i));
printf("sizeof(4):\t%d\n",sizeof(4));
printf("sizeof(4+2.5):\t%d\n",sizeof(4+2.5));
printf("sizeof(int):\t%d\n",sizeof(int));
printf("sizeof 5:\t%d\n",sizeof 5);
return ;
}
运转成果为:
[html] view plaincopysizeof(i): 4
sizeof(4): 4
sizeof(4+2.5): 8
sizeof(int): 4
sizeof 5: 4
Press any key to continue
从运转成果咱们能够看出上面的几种运用方法,实际上,sizeof核算目标的巨细也是转化成对目标类型的核算,也便是说,同种类型的不同目标其sizeof值都是相同的。从给出的代码中咱们也能够看出sizeof能够对一个表达式求值,编译器依据表达式的终究成果类型来确认巨细,可是一般不会对表达式进行核算或许当表达式为函数时并不履行函数体。如:
[html] view plaincopy#include
int print()
{
printf("Hello bigloomy!");
return 0;
}
void main()
{
printf("sizeof(print()):\t%d\n",sizeof(print()));
return ;
}
运转成果为:
[html] view plaincopysizeof(print()): 4
Press any key to continue
从成果咱们能够看出print()函数并没有被调用。
接下来咱们来看看linux内核链表里的一个宏:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
对这个宏的解说咱们大致能够分为以下4步进行解说:
1、( (TYPE *)0 ) 0地址强制 "转化" 为 TYPE结构类型的指针;
2、((TYPE *)0)->MEMBER 拜访TYPE结构中的MEMBER数据成员;
3、&( ( (TYPE *)0 )->MEMBER)取出TYPE结构中的数据成员MEMBER的地址;
4、(size_t)(&(((TYPE*)0)->MEMBER))成果转化为size_t类型。
宏offsetof的奇妙之处在于将0地址强制转化为 TYPE结构类型的指针,TYPE结构以内存空间首地址0作为开端地址,则成员地址天然为偏移地址。或许有的读者会想是不是非要用0呢?当然不是,咱们仅仅是为了核算的简洁。也能够运用是他的值,仅仅算出来的成果还要再减去该数值才是偏移地址。来看看下面的代码:
[cpp] view plaincopy#include
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)4)->MEMBER)
typedef struct stu1
{
int a;
int b;
}stu1;
void main()
{
printf("offsetof(stu1,a):\t%d\n",offsetof(stu1,a)-4);
printf("offsetof(stu1,b):\t%d\n",offsetof(stu1,b)-4);
}
运转成果为:
[cpp] view plaincopyoffsetof(stu1,a): 0
offsetof(stu1,b): 4
Press any key to continue
为了让读者加深形象,咱们这里在代码中没有运用0,而是运用的4,所以在终究核算出的成果部分减去了一个4才是偏移地址,当然实际运用中咱们都是用的是0。
懂了上面的宏offsetof之后咱们再来看看下面的代码:
[cpp] view plaincopy#include
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
typedef struct stu1
{
int a;
char b[1];
int c;
}stu1;
void main()
{
printf("offsetof(stu1,a):\t%d\n",offsetof(stu1,a));
printf("offsetof(stu1,b):\t%d\n",offsetof(stu1,b));
printf("offsetof(stu1,c):\t%d\n",offsetof(stu1,c));
printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
}
运转成果为:
[cpp] view plaincopyoffsetof(stu1,a): 0
offsetof(stu1,b): 4
offsetof(stu1,c): 8
sizeof(stu1) : 12
Press any key to continue
关于字节对齐不了解的读者或许有疑问的是c的偏移量怎样会是8和结构体的巨细怎样会是12呢?因该是sizeof(int)+sizeof(char)+sizeof(int)=9。其实这是编译器对变量存储的一个特别处理。为了进步CPU的存储速度,编译器对一些变量的开端地址做了对齐处理。在默许状况下,编译器规则各成员变量寄存的开端地址相关于结构的开端地址的偏移量有必要为该变量的类型所占用的字节数的倍数。现在来剖析下上面的代码,假如咱们假定a的开端地址为0,它占用了4个字节,那么接下来的闲暇地址便是4,是1的倍数,满足要求,所以b寄存的开端地址是4,占用一个字节。接下来的闲暇地址为5,而c是int变量,占用4个字节,5不是4的整数倍,所以向后移动,找到离5最近的8作为寄存c的开端地址,c也占用4字节,所以最终使得结构体的巨细为12。现在咱们再来看看下面的代码:
[cpp] view plaincopy#include
typedef struct stu1
{
char array[7];
}stu1;
typedef struct stu2
{
double fa;
}stu2;
typedef struct stu3
{
stu1 s;
char str;
}stu3;
typedef struct stu4
{
stu2 s;
char str;
}stu4;
void main()
{
printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
printf("sizeof(stu2) :\t%d\n",sizeof(stu2));
printf("sizeof(stu3) :\t%d\n",sizeof(stu3));
printf("sizeof(stu4) :\t%d\n",sizeof(stu4));
}
运转成果为:
[cpp] view plaincopysizeof(stu1) : 7
sizeof(stu2) : 8
sizeof(stu3) : 8
sizeof(stu4) : 16
Press any key to continue
剖析下上面咱们的运转成果,要点是struct stu3和struct stu4,在struct stu3中运用的是一个字节对齐,因为在stu1和stu3中都只要一个char类型,在struct stu3中咱们界说了一个stu1类型的 s,而stu1所占的巨细为7,所以加上加上接下来的一个字节str,sizeof(stu3)为8。在stu4中,因为咱们界说了一个stu2类型的s,而s是一个double类型的变量,占用8字节,所以接下来在stu4中选用的是8字节对齐。假如咱们此刻假定stu4中的s从地址0开端寄存,占用8个字节,接下来的闲暇地址便是8,依据咱们上面的解说可知刚好能够在此寄存str。所以变量都分配完空间后stu4结构体所占的字节数为9,但9不是结构体的鸿沟数,也便是说咱们要求分配的字节数为结构体中占用空间最大的类型所占用的字节数的整数倍,在这里也便是double类型所占用的字节数8的整数倍,所以接下来还要再分配7个字节的空间,该7个字节的空间没有运用,由编译器主动填充,没有寄存任何有意义的东西。
当然咱们也能够运用预编译指令#pragma pack (value)来告知编译器,运用咱们指定的对齐值来替代缺省的。接下来咱们来看看一段代码。
[cpp] view plaincopy#include
#pragma pack (1) /*指定按1字节对齐*/
typedef union stu1
{
char str[10];
int b;
}stu1;
#pragma pack () /*撤销指定对齐,康复缺省对齐*/
typedef union stu2
{
char str[10];
int b;
}stu2;
void main()
{
printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
printf("sizeof(stu2) :\t%d\n",sizeof(stu2));
}
运转成果为:
[cpp] view plaincopysizeof(stu1) : 10
sizeof(stu2) : 12
Press any key to continue
现在来剖析下上面的代码。因为之前咱们一向都在运用struct,所以在这里咱们特别例举了一个union的代码来剖析下,咱们咱们都知道union的巨细取决于它所有的成员中占用空间最大的一个成员的巨细。因为在union stu1中咱们运用了1字节对齐,所以关于stu1来说占用空间最大的是char str[10]类型的数组,,其值为10。为什么stu1为10而stu2却是12呢?因为在stu2的上面咱们运用了#pragma pack () ,撤销指定对齐,康复缺省对齐。所以因为stu2其间int类型成员的存在,使stu2的对齐方法变成4字节对齐,也便是说,stu2的巨细有必要在4的对界上,换句话说便是stu2的巨细要是4的整数倍,所以占用的空间变成了12。
linux操作系统文章专题:linux操作系统详解(linux不再难明)