引言
这是一次 C 语言编程中有关内存操作的巨坑记录。
问题
各位读者可以和我一起来思考以下这段小程序的输出:
int
main(void)
{
uint32_t p = 0x12345678;
uint8_t *t1 = (uint8_t *)&p;
uint8_t *t2 = t1 + 1;
uint8_t *t3 = t1 + 2;
uint8_t *t4 = t1 + 3;
printf("p:%x,t1:%x,t2:%x,t3:%x,t4:%x\n", p, *t1, *t2, *t3, *t4);
}
以下为程序的输出,各位读者有没有产生什么疑问呢?
解惑
这其实是 大小端模式 之间的区别,简单来说,世界上存在两种 CPU 一种是按照人类正常思维的方式存放数据的 大端模式(Big-Endian):数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。
还有一种就是有利于计算机处理的方式存放数据的 小端模式(Little-Endian):数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来。
还有一种 网络字节顺序 同大端模式。
综上所述,按照大端模式 p 的数据在内存中的存放方式为:
1 2 3 4 5 6 7 8
0001 0010 0011 0100 0101 0110 0111 1000
^ ^ ^ ^
t1 t2 t3 t4
按照小端模式 p 的数据在内存中的存放方式为:
7 8 5 6 3 4 1 2
0111 1000 0101 0110 0011 0100 0001 0010
^ ^ ^ ^
t1 t2 t3 t4
这时候大家能理解程序的输出了吧?hhh
需要注意的是,所有的 Intel 系列和大部分 ARM 系列的 CPU 都是按照小端模式存储的。
补充
分享一个按照小端模式正常读取数据的函数
/**
* 从内存中按照小端模式读取数据
*
* @param base 要读取的内存首址
* @param output 输出缓冲区
* @param size 要读取的字节数
* @return None
*/
void
read_little_endian(void *base, void *output, size_t size)
{
size_t i;
char t[size];
char *base_ = (char *) base;
char *output_ = (char *) output;
for (i = 1; i <= size; i++) {
t[size - i] = *base_;
base_ += 1;
}
for (i = 0; i < size; i++)
output_[i] = t[i];
}