Linux系统编程——大端小端以及判别方式
文章目录大端小端以及判别方式共用体判断数据是大端存放还是小端存放ascii表参考大端小端以及判别方式概念大端和小端的概念大端: 低位地址存放高位数据, 高位地址存放低位数据小端: 低位地址存放低位数据, 高位地址存放高位数据共用体共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位
大端小端以及判别方式
由来
关于大端小端名词的由来,网传有一个有趣的故事,可以追溯到1726年的Jonathan Swift的《格列佛游记》,其中一篇讲到有两个国家因为吃鸡蛋究竟是先打破较大的一端还是先打破较小的一端而争执不休,甚至爆发了战争。《格利佛游记》:“Lilliput和Blefuscu这两个强国在过去的36个月中一直在苦战。战争的原因是:我们都知道,吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可是那时的皇帝的祖父由于小时侯吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,由此发生了多次叛乱,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的。叛乱平息后,流亡的人就逃到这个帝国避难。据估计,先后几次有11000余人情愿si也不肯去打破鸡蛋较小的端吃鸡蛋。”Swift的《格列佛游记》其实是在讽刺当时英国(Lilliput)和法国(Blefuscu)之间持续的冲突。现在以大端、小端的命名Big-Endian、Little-Endian看,确实也符合鸡蛋的特征,一切源于生活。
概念
大端和小端的概念
大端: 低位地址存放高位数据(也叫网络字节序), 高位地址存放低位数据
小端: 低位地址存放低位数据, 高位地址存放高位数据
详解
比如向内存地址为0X1000的地址写入0X12345678这个四字节16进制数,
- - - - - - - - - ->
内存地址增长方向
大端存储
对于大端模式,即低地址存放高字节数:
低地址 | — | —> | 高地址 | |
---|---|---|---|---|
内存地址 | 0x1000 | 0x1001 | 0x1002 | 0x1003 |
存放数据 | 0x12 | 0x34 | 0x56 | 0x78 |
小端存储
对于小端存储,即低地址存储低字节数:
低地址 | — | —> | 高地址 | |
---|---|---|---|---|
内存地址 | 0x1000 | 0x1001 | 0x1002 | 0x1003 |
存放数据 | 0x78 | 0x56 | 0x34 | 0x12 |
特点
一言以蔽之,这两种模式各有各的优点。
小端模式优点:
- 内存的低地址处存放低字节,所以在强制转换数据时不需要调整字节的内容(注解:比如把int的4字节强制转换成short的2字节时,就直接把int数据存储的前两个字节给short就行,因为其前两个字节刚好就是最低的两个字节,符合转换逻辑);
- CPU做数值运算时从内存中依顺序依次从低位到高位取数据进行运算,直到最后刷新最高位的符号位,这样的运算方式会更高效
大端模式优点:符号位在所表示的数据的内存的第一个字节中,便于快速判断数据的正负和大小
其各自的优点就是对方的缺点,正因为两者彼此不分伯仲,再加上一些硬件厂商的坚持,因此在多字节存储顺序上始终没有一个统一的标准
网络字节序
不同的计算机使用的字节序可能不同,即有可能有的使用大端模式有的使用小端模式。那使用不同字节序模式的计算机如何进行通信呢? (目前个人PC大部分都是X86的小端模式)TCP/IP协议隆重出场,RFC1700规定使用“大端”字节序为网络字节序,其他不使用大端的计算机要注意了,发送数据的时候必须要将自己的主机字节序转换为网络字节序(即“大端”字节序),接收到的数据再转换为自己的主机字节序。这样就与CPU、操作系统无关了,实现了网络通信的标准化。为了程序的兼容,你会看到,程序员们每次发送和接收数据都要进行转换,这样做的目的是保证代码在任何计算机上执行时都能达到预期的效果。这么常用的操作,BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数:htons、htonl;从网络字节序到主机字节序的转换函数:ntohs、ntohl。
共用体
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
共用体有时也被称为联合或者联合体,这也是 Union 这个单词的本意。
特点
- 共用体union的存放顺序是所有成员都从低地址开始存放,利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式的读写
- 共用体所有数据共用同一块地址
- 共用体占用的内存应足够存储共用体中最大的成员
- 共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉
- 共用体在一般的编程中应用较少,在单片机中应用较多
例子1
#include <stdio.h>
union data{
int n;
char ch;
double m;
};
int main(){
union data a;
printf("%d, %d\n", sizeof(a), sizeof(union data) );
return 0;
}
输出8, 8
。
例子2
#include <stdio.h>
#include <string.h>
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
printf( "Memory size occupied by data : %d\n", sizeof(data));
return 0;
}
输出Memory size occupied by data : 20
。
例子3
#include <stdio.h>
union data{
int n;
char ch;
short m;
};
int main(){
union data a;
printf("%d, %d\n", sizeof(a), sizeof(union data) );
a.n = 0x40;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.ch = '9';
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.m = 0x2059;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.n = 0x3E25AD54;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
return 0;
}
输出
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54
这段代码不但验证了共用体的长度,还说明共用体成员之间会相互影响,修改一个成员的值会影响其他成员。
要想理解上面的输出结果,弄清成员之间究竟是如何相互影响的,就得了解各个成员在内存中的分布。以上面的 data 为例,各个成员在内存中的分布如下:
成员 n、ch、m 在内存中“对齐”到一头,对 ch 赋值修改的是前一个字节,对 m 赋值修改的是前两个字节,对 n 赋值修改的是全部字节。也就是说,ch、m 会影响到 n 的一部分数据,而 n 会影响到 ch、m 的全部数据。
思考,如果把 a.ch = '9';
移到下底,情况会是怎么样?
union data{
int n;
char ch;
short m;
};
int main(){
union data a;
printf("%d, %d\n", sizeof(a), sizeof(union data) );
a.n = 0x40;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.m = 0x2059;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.n = 0x3E25AD54;
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
a.ch = '9';//0x39
printf("%X, %c, %hX\n", a.n, a.ch, a.m);
return 0;
}
输出
不难发现,54
被39
(9的ascii码)覆盖了。但是前面的3E25AD
没有变化。共用体不是先清除后覆盖,而是有多少位,就覆盖多少位。
4, 4
40, @, 40
2059, Y, 2059
3E25AD54, T, AD54
3E25AD39, 9, AD39
注意1,一般PC机器是这样的情况,单片机情况有所不同。
注意2,超过一个字节的数据类型才有大小端存放的说法,char
是一个字节的类型,是没有大小端的说法的,int
是4个字节,是有大小端的说法的。
判断数据是大端存放还是小端存放
代码1
判断的思路是确定一个多字节的值(下面使用的是4字节的整数),将其写入内存(即赋值给一个变量),然后用指针取其首地址所对应的字节(即低地址的一个字节),判断该字节存放的是高位还是低位,高位说明是Big endian,低位说明是Little endian。
#include <stdio.h>
int main ()
{
unsigned int x = 0x12345678;
char *c = (char*)&x;
if (*c == 0x78) {
printf("Little endian");
} else {
printf("Big endian");
}
return 0;
}
代码2
共用体union的存放顺序是所有成员都从低地址开始存放,利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式的读写
#include <stdio.h>
#include <stdlib.h>
union {
short s;
char c[sizeof(short)];
} un2;
union {
int s;
char c[sizeof(int)];
}un4;
int main()
{
printf("[%d][%d][%d]\n", sizeof(short), sizeof(int), sizeof(long int));
//测试short类型
un2.s = 0x0102;// 0x0102 =? 16*16+2
printf("%d,%d,%d\n",un2.c[0],un2.c[1],un2.s);
//测试int类型
//un4.s = 0x12345678;
un4.s = 0x01020304;
printf("%d,%d,%d,%d,%d\n", un4.c[0], un4.c[1], un4.c[2], un4.c[3], un4.s);
return 0;
}
输出
[2][4][8]
2,1,258
4,3,2,1,16909060
我们可以看出,un4.c[0]
对应16进制数里的04
,un4.c[1]
对应16进制数03
,un4.c[2]
对应16进制数02
,un4.c[3]
对应16进制数01
,低位地址存放低位数据, 高位地址存放高位数据,所以当前计算机为小端存放数据。
思考,为什么用共用体?注意,机器会有情况也不同,intel芯片通常是小端(修改分区表时要注意),单片机一般为大端。
ascii表
Bin(二进制) | Oct(八进制) | Dec(十进制) | Hex(十六进制) | 缩写/字符 | 解释 |
---|---|---|---|---|---|
0011 0001 | 061 | 49 | 0x31 | 1 | 字符1 |
0011 0010 | 062 | 50 | 0x32 | 2 | 字符2 |
0011 0011 | 063 | 51 | 0x33 | 3 | 字符3 |
0011 0100 | 064 | 52 | 0x34 | 4 | 字符4 |
0011 0101 | 065 | 53 | 0x35 | 5 | 字符5 |
0011 0110 | 066 | 54 | 0x36 | 6 | 字符6 |
0011 0111 | 067 | 55 | 0x37 | 7 | 字符7 |
0011 1000 | 070 | 56 | 0x38 | 8 | 字符8 |
0011 1001 | 071 | 57 | 0x39 | 9 | 字符9 |
参考
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)