1、字节对齐
1、什么是字节对齐
内存空间是按字节划分,理论上可以从任意起始地址访问任意类型的变量。但实际上在访问特定类型变量是经常在特定的内存地址访问,这就需要各种数据类型按照一定的规则进行排列,而不是一个接一个紧接着排放。简单的讲就是数据存放的地址要满足一定的规则。
例如32位的计算机处理器通过总线访问内存数据。每个总线周期从偶地址开始访问32位数据,如果一个32位的数据没有存放在4字节整除的内存地址处,那么处理器就需要两个总线周期对其进行访问,显然会降低访问效率。因此,通过合理的内存对齐可以提高访问效率。为使得cpu对数据能够进行快速访问,数据的起始地址应该遵循对齐规则。比如,4字节数据的起始地址应该能被4整除。字节对齐需要考虑处理器的类型和编译器的类型。
2、字节对齐的作用:
(1)合理的内存对齐可以提高cpu访问效率
(2)~可以节省存储空间
(3)可以减少cpu访问内存的出错概率
字节对齐最主要反映在数据结构对齐,字节对齐原则如下:
1、基本数据类型自身对齐值:按照自身数据长度作为对齐值
2、结构体或类的自身对齐值:其成员中自身对齐值最大的最大值
3、指定对齐值:#pragma pack(value)指定对齐值为value
4、数据成员、结构体和类的有效对齐值:自身对齐值和有效对齐值中较小者
#include <stdio.h>
#include <stdlib.h>
#define debug_printf(value) printf(#value " ---==> %d\n", value)
#define debug_struct_member_offset(struct, member) (((char *)(&(((struct *)0)->member))) - ((char *)0))
typedef struct data_type1{
char a[2];
short b;
int c;
} Data_type1;
typedef struct data_type2{
short a;
int b;
char c[2];
} Data_type2;
int main(int argc, char** argv){
int a,b,c;
a = debug_struct_member_offset(Data_type1,a);
b = debug_struct_member_offset(Data_type1,b);
c = debug_struct_member_offset(Data_type1,c);
debug_printf(sizeof(Data_type1));
debug_printf(a);
debug_printf(b);
debug_printf(c);
a = debug_struct_member_offset(Data_type2,a);
b = debug_struct_member_offset(Data_type2,b);
c = debug_struct_member_offset(Data_type2,c);
debug_printf(sizeof(Data_type2));
debug_printf(a);
debug_printf(b);
debug_printf(c);
}
64位ubuntu gcc
可以看到两个结构体里的成员变量类型相同,但是顺序不同。因为数据对齐的规则,最后俩个结构体所占空间大小不同。
eg;数组和字符串作为结构体成员,并不是按照数组长度或字符串长度进行对齐。
typedef struct data_type3{
char a;
char b;
char c;
} Data_type3;
typedef struct data_type4{
char a;
char b[2];
} Data_type4;
比如上面两个结构体占用的字节大小都i是3
3、对对齐规则进行干涉的方法
(1)使用prama pack(value)伪指令
(2)使用__attribute((aligned(n))),操作对象为结构体时,是对结构体的对齐值修改为n
(3)位域
#include <stdio.h>
#include <stdlib.h>
#define debug_printf(value) printf(#value ": %d\n",value)
#define debug_struct_member_offset(struct, member) (((char*)(&(((struct *)0)->member)))- ((char *)0))//结构体成员相对结构体起始地址偏移量
#pragma pack(4) //4字节对齐开始,对齐大小只能比4小,不能大
typedef struct
{
int a;
double b;
} test_t;
/*4字节对齐,所以即使b是double类型,偏移地址是4而不是8*/
#pragma pack()//字节对齐结束
typedef struct
{
char a;
short b;
}__attribute__((packed)) test_t1; //按实际空间
typedef struct
{
char a;
short b;
}__attribute__((aligned(4))) test_t2;
typedef struct
{
char a;
short b;
}__attribute__((aligned(8))) test_t3;
typedef struct
{
char a;
short b;
}__attribute__((aligned(16))) test_t4;
int main(void)
{
int a,b,c;
a = debug_struct_member_offset(test_t,a);
b = debug_struct_member_offset(test_t,b);
debug_printf(sizeof(test_t));
debug_printf(a);
debug_printf(b);
a = debug_struct_member_offset(test_t1,a);
b = debug_struct_member_offset(test_t1,b);
debug_printf(sizeof(test_t1));
debug_printf(a);
debug_printf(b);
a = debug_struct_member_offset(test_t2,a);
b = debug_struct_member_offset(test_t2,b);
debug_printf(sizeof(test_t2));
debug_printf(a);
debug_printf(b);
a = debug_struct_member_offset(test_t3,a);
b = debug_struct_member_offset(test_t3,b);
debug_printf(sizeof(test_t3));
debug_printf(a);
debug_printf(b);
a = debug_struct_member_offset(test_t4,a);
b = debug_struct_member_offset(test_t4,b);
debug_printf(sizeof(test_t4));
debug_printf(a);
debug_printf(b);
return 0;
}
扩展:attribute可以设置函数属性、变量属性和类型属性
对于属性(主要是函数属性)主要设置的关键字有
alias:设置函数别名
aligned:设置对齐方式
constructor/destructor:主函数执行之前、之后执行的函数
format:指定变参函数的格式输入字符串所在函数位置以及对应格式输出的位置
noreturn:指定这个函数没有返回值
例如:在main函数执行前和执行后运行的函数
#include <stdio.h>
void before() __attribute__((constructor));
void after() __attribute__((destructor));
int main(void)
{
printf("this is function %s\n",__FUNCTION__);
return 0;
}
void before(){
printf("this is function %s\n",__FUNCTION__);
return;
}
void after(){
printf("this is function %s\n",__FUNCTION__);
return;
}
4、位域
出现于结构体或共用体的定于中,形式如下
struct student
{
int sex : 1;
int id : 7;
int score : 8;
};
在结构体student中,成员变量sex占用空间位1位,id7位,score8位。
其中,位域的值必须小于变量类型的宽度。比如int类型成员变量的位域值必须是1~31之间的整数
位域的填充规则
1、如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2、如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3、如果相邻的两个位域字段的类型不同,则各个编译器的具体实现有差异。不压缩方式,采用前面所讲的字节对齐方式。
4、整个结构体的对齐方式使用结构体的有效对齐参数值。
5、如果位域字段之间穿插着非位域字段,则不进行压缩,该字段采用字节对齐。
6、无名的位域不能使用,只能用于填充,位宽为0表示强制下一位域对齐到当前类型的边界。
2、字节序
大端小端的概念是面向多字节数据裂隙的存储方式定义的
小端就是低位在前(”前“是指靠近内存低地址),既低位字节存储在内存低地址,字节高低顺序与内存高低顺序相同。
大端就是高位在前。
网络字节序规定是大端模式,即高字节先走。
#include <stdio.h>
int byteorder()
{
int a = 1;
return *(char*)&a;
}
int main(void)
{
int ret = byteorder();
if( ret == 1 )
printf("小端\n");
else
printf("大端\n");
return 0;
}
不同的cpu有不同的字节序类型,我们常用的x86和arm架构都是采用小端存储。