小麦大叔
认证:普通会员
所在专题目录 查看专题
小小舵机,大大玄机
UART协议快速扫盲(图文并茂+超详细)
I2C协议快速扫盲
原来SPI并没有我想的那么简单
芯片的大小端到底是怎么回事?
别找了,这篇SPWM入门教程完全够用
作者动态 更多
凉了!嵌入式真的要卷成下一个Java了吗?
02-28 09:03
嵌入式开发必须学习Qt吗?
02-27 09:16
这么简单的环境搭建,网友:我却搭了很久,结果还是不行...
02-26 15:44
单片机如何能运行如飞?一种高效实现数学函数的方式!
2022-01-26 11:08
keil开发过程中map和elf文件有什么作用?
2021-11-01 19:05

芯片的大小端到底是怎么回事?

本文将带你了解系统大小端的原理以及字节序,如何在程序中对大小端进行检测。如果觉得不错,欢迎关注、分享、收藏、点赞。希望能帮助到大家,如有错误敬请指出,谢谢!

目录

  • 先说内存
  • 字节
  • 字节顺序
  • 大端和小端的区分

先说内存

程序运行在内存中,计算机中的最小存储单位是Bit,即10的二进制,它可以识别的机器码就是以二进制形式存储的;

内存由多个存储单元组成,每个存储单元都有一个唯一的数字地址字节可寻址内存。每个存储位置可以包含固定数量的二进制数字。

在大多数的现代计算机上,地址的最小数据的长度为8位,称为字节(1 Byte = 8 Bit);

一般计算机中用户程序直接访问的地址是虚拟内存的地址,操作系统内核会根据用户程序访问的虚拟地址,找出页表中对于的物理地址,最终寻址到所需要的数据;具体如下图所示;

然而,在MCU等裸机开发的环境中,没有MMU,则程序直接访问的是物理内存,所以无论是计算机还是MCU在程序运行中都需要内存作为载体,保存数据和运行程序。那么,下面再来看是程序以及数据在内存中是以何种形式存储的?

字节

前面提到过,在大多数的现代计算机上,地址的最小数据的长度为8位,称为字节(1 Byte = 8 Bit);至于为什么是8位?看起来似乎有点玄学,并且很吉利的一个数字,但是老外好像没有数字迷信,这里的原因大概是因为这几点;

  • 由于计算机内部最本质需要实现的操作是加法,减法,乘法,除法等运算都能通过加法实现,另外由于最早期设计的加法器是8位;

  • 另外一个原因可以追溯到1956年,IBM公司最早提出字节的概念,随着IBM的壮大,字节便专门用来表示二进制数,其中也包括不少优点;易于以BCD码形式保存;用于保存文本也非常合适,另外世界上大部分语言都可以用小于256个字符(一个字节宽度)来表示,如果一个不够,那就两个,比如中文;

    [^1]: 编码 第15章 字节与十六进制

字节顺序

在说大小端之前,要先提一下字节顺序(Endianness),它是描述数据以字节为一组在计算机内存中存储顺序的术语。

字节顺序可以是大端顺序(big-endian)或者小端顺序(little-endian);在对多字节数据进行存储时,一般遵循以下规则;

  • 小端:数据的最后一个字节先存储,即LSB
  • 大端:数据的第一个字节先存储,即MSB

数据0x01020304分别在大端机器和小端机器中的存储形式,具体如下图所示;

在大多数情况下,编译器会处理字节顺序,从而避免出现大小端不一致的问题,但是在以下情况下字节顺序就会成为一个问题。

在通讯中,例如网络编程:假设在小端机器上向文件写入整数,然后将此文件传输到大端机器上。如果没有做大小端转换,那么大端机器就会以相反的顺序读取文件

TCP/IP协议中,默认使用的是大端顺序,它与具体的CPU类型、操作系统等无关;

那么如何在程序中快速的区分大小端呢?

大端和小端的区分

下面介绍几种通过C语言实现大小端判断的方法;

第一种通过指针的内存对齐来实现;

函数的形式;

unsigned char check_endian( void )
{
    int test_var = 1;
    unsigned char *test_endian = (unsigned char*)&test_var;

    return (test_endian[0] == 0);
}

宏定义的形式;

static uint32_t endianness = 0xdeadbeef; 
enum endianness { BIG, LITTLE };

#define ENDIANNESS ( *(const char *)&endianness == 0xef ? LITTLE \
                   : *(const char *)&endianness == 0xde ? BIG \
                   : assert(0))

更加简洁;

#define IS_BIG_ENDIAN (!*(unsigned char *)&(uint16_t){1})

第二种通过结构体和联合体的内存对齐来实现;

#ifndef ORDER32_H
#define ORDER32_H

#include <limits.h>
#include <stdint.h>

#if CHAR_BIT != 8
#error "unsupported char size"
#endif

enum
{
    O32_LITTLE_ENDIAN = 0x03020100ul,
    O32_BIG_ENDIAN = 0x00010203ul,
    O32_PDP_ENDIAN = 0x01000302ul,      /* DEC PDP-11 (aka ENDIAN_LITTLE_WORD) */
    O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 (aka ENDIAN_BIG_WORD) */
};

static const union { unsigned char bytes[4]; uint32_t value; } o32_host_order =
    { { 0, 1, 2, 3 } };

#define O32_HOST_ORDER (o32_host_order.value)

#endif

当然具体的方法还有很多,本文就先讲到这里。


声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 3
收藏 3
关注 132
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧