Linux系统编程——大端小端以及判别方式

大端小端以及判别方式

由来

关于大端小端名词的由来,网传有一个有趣的故事,可以追溯到1726年的Jonathan Swift的《格列佛游记》,其中一篇讲到有两个国家因为吃鸡蛋究竟是先打破较大的一端还是先打破较小的一端而争执不休,甚至爆发了战争。《格利佛游记》:“Lilliput和Blefuscu这两个强国在过去的36个月中一直在苦战。战争的原因是:我们都知道,吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可是那时的皇帝的祖父由于小时侯吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,由此发生了多次叛乱,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的。叛乱平息后,流亡的人就逃到这个帝国避难。据估计,先后几次有11000余人情愿si也不肯去打破鸡蛋较小的端吃鸡蛋。”Swift的《格列佛游记》其实是在讽刺当时英国(Lilliput)和法国(Blefuscu)之间持续的冲突。现在以大端、小端的命名Big-Endian、Little-Endian看,确实也符合鸡蛋的特征,一切源于生活。

概念

	大端和小端的概念
		大端: 低位地址存放高位数据(也叫网络字节序), 高位地址存放低位数据
		小端: 低位地址存放低位数据, 高位地址存放高位数据

详解

比如向内存地址为0X1000的地址写入0X12345678这个四字节16进制数,

	- - - - - - - - - ->
	内存地址增长方向

大端存储
对于大端模式,即低地址存放高字节数:

低地址—>高地址
内存地址0x10000x10010x10020x1003
存放数据0x120x340x560x78

小端存储
对于小端存储,即低地址存储低字节数:

低地址—>高地址
内存地址0x10000x10010x10020x1003
存放数据0x780x560x340x12

特点

一言以蔽之,这两种模式各有各的优点。

小端模式优点

  1. 内存的低地址处存放低字节,所以在强制转换数据时不需要调整字节的内容(注解:比如把int的4字节强制转换成short的2字节时,就直接把int数据存储的前两个字节给short就行,因为其前两个字节刚好就是最低的两个字节,符合转换逻辑);
  2. 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;
}

输出
不难发现,5439(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进制数里的04un4.c[1]对应16进制数03un4.c[2]对应16进制数02un4.c[3]对应16进制数01,低位地址存放低位数据, 高位地址存放高位数据,所以当前计算机为小端存放数据。
在这里插入图片描述

思考,为什么用共用体?注意,机器会有情况也不同,intel芯片通常是小端(修改分区表时要注意),单片机一般为大端。

ascii表

Bin(二进制)Oct(八进制)Dec(十进制)Hex(十六进制)缩写/字符解释
0011 0001061490x311字符1
0011 0010062500x322字符2
0011 0011063510x333字符3
0011 0100064520x344字符4
0011 0101065530x355字符5
0011 0110066540x366字符6
0011 0111067550x377字符7
0011 1000070560x388字符8
0011 1001071570x399字符9

参考

C语言共用体(C语言union用法)详解

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值