大小端存储以及字节对齐

大小端存储以及字节对齐

  • 一、大端存储和小端存储
    • 1. 什么是大端存储、小端存储?
    • 2. 为什么会有大端存储、小端存储?
    • 3. 如何判断是大端存储还是小端存储以及如何实现大小端转换?
  • 二、字节对齐
    • 1. 什么是字节对齐
    • 2. 为什么要字节对齐
    • 3. 结构体字节对齐规则
    • 4. 计算结构体的大小实例
  • 总结

一、大端存储和小端存储

1. 什么是大端存储、小端存储?

  • 大端存储(Big endian):数据的低位(低字节)存储在内存中的高地址,数据的高位(高字节)存储在内存中的低地址。
    即:低位 —> 高地址;高位 —> 低地址
  • 小端存储(Little endian):数据的低位(低字节)存储在内存中的低地址,数据的高位(高字节)存储在内存中的高地址。
    即:低位 —> 低地址;高位 —> 高地址

比如:int data = 0x01020304; 此时,变量data最高位是01,最低位是04。假设变量data存储在计算机的首地址为1001H。

  1. 在大端存储模式下,变量data在计算机内存地址1001H到1004H中为0x01020304
    大小端存储以及字节对齐_第1张图片
  2. 在小端存储模式下,变量data在计算机内存地址1001H到1004H中为0x04030201。
    大小端存储以及字节对齐_第2张图片

2. 为什么会有大端存储、小端存储?

  在计算机系统中,规定:每个地址单元都会对应一个字节。在C语言中,除了有一个字节的char类型,也有多个字节的数据类型。对于16位或者32位的处理器,由于寄存器的宽度大于一个字节,那么就存在如何将一个多字节变量的数据如何存放的问题——所以,就有了大小端之分。

3. 如何判断是大端存储还是小端存储以及如何实现大小端转换?

  大端模式还是小端模式主要取决于CPU处理架构,我们常用的x86架构是小端模式,很多的RAM开发板也是小端模式;而在网络编程中使用的网络字节序指的就是大端模式,往往需要主机字节序和网络字节序的相互转换(如:htons()、ntohs()、htonl()、ntohl()等)。以下显示了用代码如何判断大小端存储以及大小端转换。

//	Test1.c
#include 

//	常规方法判断大小端
//	强制转换char*类型,*pData 获取首地址的存储内容
void Test01(){
     
	int data = 1;
	char *pData = (char *)&data; 	
	if(*pData == 1){
     	//	首地址存储内容是01,小端
		printf("Little endian\n");
	}else if(*pData == 0){
     	//	首地址存储内容是00,大端
		printf("Big endian\n");
	}
}

//	利用联合体判断大小端。
//	联合体中的所有数据成员共用同一块内存(成员变量中最大的内存)
void Test02(){
     
	union Endian{
     
		int  data;
		char item;
	};
	union Endian check;
	check.data = 1;
	if(check.item == 1){
     
		printf("Little endian\n");
	}else if(check.item == 0){
     
		printf("Big endian\n");
	}
}

//	int 类型的大小端相互转换
int Test03(int Val){
     
	int res = 0;
	res = ((Val& 0x000000ff) << 24) +	\
		((Val& 0x0000ff00) << 8) +	\
		((Val& 0x00ff0000) >> 8) +	\
        ((Val& 0xff000000) >> 24);
	return res;
}

int main(){
     
	Test01();
	Test02();
	printf("%d\n", Test03(1));	//	0x00000001 --> 0x01000000
	return 0;
}

二、字节对齐

1. 什么是字节对齐

  我们先来看一个结构体,这个结构体一共占用多少字节?

struct Data{
     
	char ch;	//	1 Byte
	int	 val;	//	4 Byte
};
printf("sizeof:%d\n", sizeof(Data));	//	8

sizeof 打印出这个结构体占用了8个字节,其中包含5字节的数据和3字节的填充,这种情况就是内存对齐,它在内存中的分布情况如下(ch还是只占用1个字节,val占用4字节):
大小端存储以及字节对齐_第3张图片

2. 为什么要字节对齐

  计算机中的内存是以一个字节为单位,但在读取数据时是一段内存一段内存读取,从而在保证字节对齐时提升数据读取效率,但也会消耗一部分的空间资源。如:对于上述的Data结构体,假设计算机读取内存是4个字节读取,在字节对齐时在访问数据val时,只需要一次读取4字节;而在非字节对齐时,需要一次读取前4个字节,获取后3字节,再一次读取后4个字节,获取前1字节,最后将3字节的val数据和1字节的val数据拼接而成。

3. 结构体字节对齐规则

  1. 结构体第一个数据成员存储在偏移为0的位置,后续每个数据成员存储在偏移为对齐数的整数倍。
  2. 对齐数为该数据成员所占字节数,也可以通过#pragma pack(n)设置编译器对齐数,通过#pragam pack()关闭。
  3. 结构体的大小必须是结构体对齐数的整数倍。
  4. 结构体的对齐数为结构体内部成员中的最大对齐数。

4. 计算结构体的大小实例

//	Test2.c
#include 

// #pragma pack(n)
// 对齐数分别为 1, 4, 1;	结构体对齐数为 4;
struct A{
     
	char a;	//	0-3	 填充3Byte	规则2	
	int  b;	//	4-7	
	char c;	//	8-11 填充3Byte	规则3
};			
//	#pragma	pack()

// 对齐数分别为 1, 1, 4;	结构体对齐数为 4;
struct B{
     	
	char a;	//	0
	char c;	//	1-3	填充2Byte	规则2
	int  b;	//	4-7
};			

// 对齐数分别为 1, 4, 4;	结构体对齐数为 4;
struct C{
     
	char a;		//	0-3	填充3Byte	规则2
	struct A d;	//	4-15
	int	 b;		//	16-19
};

// 对齐数分别为 4, 8, 1; 结构体对齐数为 8;
struct D{
     
	int a;		//	0-7		填充4Byte	规则2
	double b[4];//	8-39
	char ch[5]; //	40-47	填充3Byte	规则3
};
int main(){
     
	printf("sizeof(A):%ld\n", sizeof(struct A));	//	12
	printf("sizeof(B):%ld\n", sizeof(struct B));	//	8
	printf("sizeof(C):%ld\n", sizeof(struct C));	//	20
	printf("sizeof(D):%ld\n", sizeof(struct D));	//	48
	return 0;
}

//	对于结构体A和B,数据成员类型均相同次序不同,结构体B更节省内存空间。
//	因此,定义结构体,将数据成员类型所占字节数从小到大排列可以减少内存空间。

总结

  1. 大端模式:低位 —> 高地址;高位 —> 低地址
  2. 小端模式:低位 —> 高地址;高位 —> 低地址
  3. 字节对齐是计算机一种空间换时间的方法。在定义结构体时,将数据成员类型所占字节数从小到大排列,可以减少结构体所占内存。

你可能感兴趣的:(笔记,c语言)