数据存储的大小端模式
一、大小端概念
-
大端模式(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");
}
}
}