数据存储的大小端模式

本文介绍了数据存储的大小端模式,大端模式将高位字节存储在低地址,小端模式则相反。CPU的大小端通常由架构决定,如IBM、Sun使用大端,ARM、X86使用小端。通过一个简单的CPU大小端判定示例进行了说明。此外,文章详细展示了NEON Raw12数据在小端模式下的16位读取过程,并给出了相关代码实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据存储的大小端模式


一、大小端概念

  • 大端模式(Big-Endian): IBM、 Sun、 PowerPC 架构的处理器一般都采用大端模式

    • 指数据的⾼字节保存在内存的低地址中,⽽数据的低字节保存在内存的⾼地址
    • 这样的存储模式把数据当作字符串顺序处理:地址由⼩向⼤增加,⽽数据从⾼位往低位放,这和我们的阅读习惯⼀致
    • 符号位的判定固定为第一个字节,容易判断正负
  • 小端模式(Little-Endian): ARM、 X86、 DSP 一般都采用小端模式

    • 指数据的⾼字节保存在内存的⾼地址中,⽽数据的低字节保存在内存的低地址
    • 这种存储模式将地址的⾼低和数据位权有效地结合起来,⾼地址部分权值⾼,低地址部分权值低
    • 强制转换数据不需要调整字节内容,1、2、4 字节的存储方式一样
  • 高低字节: 是对于字节数⼤于 1 的数据来说的,1 字节⽆⾼低字节之分

  • 大小端数据的存储与读取:

unsigned int value = 0x12345678;

# Big-Endian: 低地址存放⾼位字节,如下:
0x12  # 低地址,存放⾼位字节
0x34
0x56
0x78  # 高地址,存放低位字节
# 从低地址开始读取,得到的结果是 0x12345678;从高地址开始读取,得到的结果是 0x78563412
# 一般大端模式,从低地址开始读取(从左往右读取)

# Little-Endian: 低地址存放低位字节,如下:
0x78  # 低地址,存放低位字节
0x56
0x34
0x12  # 高地址,存放高位字节
# 从低地址开始读取,得到的结果是 0x78563412;从高地址开始读取,得到的结果是 0x12345678
# 一般小端模式,从高地址开始读取(从右往左读取)

在这里插入图片描述


二、CPU 大小端判定

  • 联合体所有成员共享同⼀块空间(起始地址一致),利⽤该特性就可以轻松地获得 CPU 对内存采⽤ Little- endian 还是 Big-endian 模式存储
  • 大小端主要由 CPU 决定,一般操作系统都是小端,而通讯协议是大端的;一般 X86/ARM/DSP 是小端模式,而 KEIL C51 则为大端模式,有些 ARM 处理器还可以由硬件来选择是大端模式还是小端模式
// 变量 i 占 4 个字节,但只有一个字节的值为 1,另外 3 个字节的值都为 0
// 取联合体前 8 位,即第一个字节,判断是否等于 1,⼤端返回 0,⼩端返回 1
int checkCPU(void) {
    union {
        int i;
        char ch;
    } c;

    c.i = 1;  // 小端存储:0x01 00 00 00, int 占用 32 位

    return c.ch == 1;  
}

  • 一般 IDE 中的内存地址都是从低到到高进行排列的,大小端以及如何读取示例如下:
    在这里插入图片描述

三、NEON Raw12toRaw16 小端读取示例

  • raw12 数据展示及小端模式读取(从右往左看)
# raw12 数据原始内存中的值
0xEA, 0xC0, 0x0E, 0xF3, 0xC0, 0x0E,
0xE7, 0xB0, 0x0E, 0xF4, 0x40, 0x0F,
0xF5, 0xF0, 0x0E, 0xF3, 0x80, 0x0E,
0xF3, 0xF0, 0x0E, 0xEC, 0x60, 0x0F,
0xF8, 0xF0, 0x0E, 0xEA, 0xF0, 0x0E,
0xF1, 0xC0, 0x0E, 0xF0, 0x00, 0x0F,
0xE2, 0x40, 0x0F, 0xE5, 0xF0, 0x0E,
0xE9, 0x60, 0x0F, 0xEA, 0xD0, 0x0E


# raw12 偏移一个字节后内存中的值
0xC0, 0x0E, 0xF3, 0xC0, 0x0E, 0xE7, 
0xB0, 0x0E, 0xF4, 0x40, 0x0F, 0xF5, 
0xF0, 0x0E, 0xF3, 0x80, 0x0E, 0xF3, 
0xF0, 0x0E, 0xEC, 0x60, 0x0F, 0xF8, 
0xF0, 0x0E, 0xEA, 0xF0, 0x0E, 0xF1, 
0xC0, 0x0E, 0xF0, 0x00, 0x0F, 0xE2, 
0x40, 0x0F, 0xE5, 0xF0, 0x0E, 0xE9, 
0x60, 0x0F, 0xEA, 0xD0, 0x0E, 0x50  # 内存偏移一个字节后越界的数值



# 小端模式下,为了和人的阅读习惯一致(高地址在前,对应高位数据;低地址在后,对应低位数据),应该从右往左看(相当于将原本内存中的数据做个镜像)
# 从右边往左看,u8 在内存中的排列为:0x0E, 0xC0, 0xF3, 0x0E, 0xC0, 0xEA;
# 从右边往左看,u12 读取的话,得到数据 0x0EC, 0x0F3, 0x0EC, 0x0EA 
# 从右边往左看,u16 读取的话,得到数据 0x0EC0, 0xF30E, 0xC0EA


# u16 读取的数据
c0ea 	f30e 	0ec0 	b0e7 	f40e 	0f40 	f0f5 	f30e 	
0e80 	f0f3 	ec0e 	0f60 	f0f8 	ea0e 	0ef0 	c0f1 	
f00e 	0f00 	40e2 	e50f 	0ef0 	60e9 	ea0f 	0ed0 	


# u16 偏移一个字节的读取数据
c0 0e b0 0e   f0 0e f0 0e   f0 0e c0 0e   40 0f 60 0f   
f3 c0 f4 40   f3 80 ec 60   ea f0 f0 00   e5 f0 ea d0  
0e e7 0f f5   0e f3 0f f8   0e f1 0f e2   0e e9 0e 50


# 转换结果
# 10 进制显示
234 	236 	243 	236 	231 	235 	244 	244 	
245 	239 	243 	232 	243 	239 	236 	246 	
248 	239 	234 	239 	241 	236 	240 	240 	
226 	244 	229 	239 	233 	246 	234 	237 

# 16 进制显示
00ea 	00ec 	00f3 	00ec 	00e7 	00eb 	00f4 	00f4 	
00f5 	00ef 	00f3 	00e8 	00f3 	00ef 	00ec 	00f6 	
00f8 	00ef 	00ea 	00ef 	00f1 	00ec 	00f0 	00f0 	
00e2 	00f4 	00e5 	00ef 	00e9 	00f6 	00ea 	00ed 	
  • raw12toraw16 代码实践
#include <stdio.h>
#include <NEON_2_SSE.h>

int main() {
    // 1、定义内存中的数据及结果字符数组变量
    // This array represents a sizeof 96 u8 hwc image(4*8*3)
    uint8_t pu8SrcImg[48] = {0xEA, 0xC0, 0x0E, 0xF3, 0xC0, 0x0E,
                             0xE7, 0xB0, 0x0E, 0xF4, 0x40, 0x0F,
                             0xF5, 0xF0, 0x0E, 0xF3, 0x80, 0x0E,
                             0xF3, 0xF0, 0x0E, 0xEC, 0x60, 0x0F,
                             0xF8, 0xF0, 0x0E, 0xEA, 0xF0, 0x0E,
                             0xF1, 0xC0, 0x0E, 0xF0, 0x00, 0x0F,
                             0xE2, 0x40, 0x0F, 0xE5, 0xF0, 0x0E,
                             0xE9, 0x60, 0x0F, 0xEA, 0xD0, 0x0E};

    uint16_t pu16DstImg[48] = {0};

    uint16x8x4_t u16x8x4dst;
    uint16x8x3_t u16x8x3src;

    // 一次读取 3*8=24 个 u16 数据(从内存加载到寄存器,交织读取),偏移量为 i
    u16x8x3src = vld3q_u16((uint16_t *) pu8SrcImg);
    u16x8x4dst.val[0] = vshrq_n_u16(vshlq_n_u16(u16x8x3src.val[0], 4), 4); // 左移4位 再右移4位
    u16x8x4dst.val[3] = vshrq_n_u16(u16x8x3src.val[2], 4);                 // 右移4位

    u16x8x3src = vld3q_u16((uint16_t *) (pu8SrcImg + 1));
    u16x8x4dst.val[1] = vshrq_n_u16(u16x8x3src.val[0], 4);                 // 右移4位
    u16x8x4dst.val[2] = vshrq_n_u16(vshlq_n_u16(u16x8x3src.val[1], 4), 4); // 左移4位 再右移4位
    vst4q_u16(pu16DstImg, u16x8x4dst);

    for (int i = 0; i < 32; i++) {
        printf("%d 	", pu16DstImg[i]);
        if ((i + 1) % 8 == 0) {
            printf("\n");
        }
    }
}

四、参考资料

1、详解大端模式和小端模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值