计算机系统中的地址和字节序(字节是计算机中)

小编:霸主 更新时间:2022-09-21

对于程序中跨越多个字节的对象,必须建立两个常识:

  1. 这个对象的地址是什么;
  2. 内存中的字节是如何排列的

在所有的机器中,多字节对象是以连续内存进行存储的,最小的字节地址就是对象的地址。比如定义

int x = 0x01234567;

假设它的地址是0x100,也就是说对x进行取地址(&x)等于0x100,假设int类型有3个字节,那么其它字节在内存0x101, 0x102, 0x103的位置。

对于这个对象,有两种常规的排列方式,定义一个w为的整形[Xw-1, Xw-2, ..., X1, X0],Xw-1是最高位,X0是最低位。假设w是8的整数倍,那么这些位可以被分为多个字节,最高字节可以表示为[Xw-1, Xw-2, ..., Xw-8],最低字节可以表示为[X7, X6, ..., X0]。

有些机器从最低字节到最高字节存储,另外一些机器从最高字节到最低字节存储,前者称为(Little Endian)小端,后者称为(Big Endian)大端。

假设x的值为0x01234567,那么字节的分布如下

计算机系统中的地址和字节序(字节是计算机中)

大小端

大端的最高字节是0x01,而小端是0x67。

多数的Intel机器是小端,多数的IBM或者Oracle是大端,最近很多微处理器是双端模式,意味着它们可以被配置为大端或者小端。实际上,一旦操作系统被确定,那么机器的大小端也随之确定。比如ARM微处理器的硬件既可以是大端,也可以是小端,但是最常用的两大系统Android和Apple都是小端。

人们对于哪种字节序正确,投入了极端的情绪,大端和小端的说法来自于小说《格列弗游记》,他们在讨论熟鸡蛋是从大端打开还是从小端打开,还由此引发战争。和鸡蛋问题类似,选择哪种顺序并不是技术问题,而是不同人的习惯。

计算机系统中的地址和字节序(字节是计算机中)

Endian

只要一种排序被选择,然后一直坚持,那么无论哪种选择都是可以的。

对于大多数的应用开发者来说,字节序问题是不会碰到的,运行的程序都能给出相同的答案。然后有时候,程序员需要考虑字节序。

第一个例子是二进制数据在不同的机器间进行网络传输,如果一个小端机器将数据送给大端机器,那么就会出现问题,另外一端会收到相反的数据。为了避免这个问题,网络传输需要定义一种标准的字节序,然后机器根据字节序的不同进行转换。

第二个例子是查看一个整形的字节序列,经常在底层程序中见到。比如x86-64处理器的底层代码

4004d3: 01 05 43 0b 20 00 add %eax,0x200b43(%rip)

上述语句是通过反汇编器生成的,生成程序的可执行序列。01 05 43 0b 20 00的意思是将地址0x200b43的值和程序计数器(寄存器)相加,得到值存入到0x200b43中。

如果我们拿到最后四个字节43 0b 20 00,取相反的顺序得到00 20 0b 43,去掉开头的00,得到0x200b43地址。对于小端的机器来说,汇编程序的地址都是相反的。通常来说最低位在左边,最高位在右边,但是小端的机器与之相反,最高位在右边,最低位在左边。

第三个例子就是类型转换或者C语言中的Union关键字,运行对象的不同部分被访问,不鼓励应用程序这样进行编程,但是有时对于系统编程来说是必须的。

下图展示了如何通过类型转换访问和打印不同对象的字节地址的内容

#include typedef unsigned char* byte_pointer; void show_bytes(byte_pointer start, size_t len) { int i; for (i = 0; i < len; i++) printf(" %.2x", start[i]); printf("\n"); } void show_int(int x) { show_bytes((byte_pointer) &x, sizeof(int)); } void show_float(float x) { show_bytes((byte_pointer) &x, sizeof(float)); } void show_pointer(void* x) { show_bytes((byte_pointer) &x, sizeof(void*)); } void test_show_bytes(int val) { int i_val = val; auto f_val = (float) i_val; int* p_val = &i_val; show_int(i_val); show_float(f_val); show_pointer(p_val); } int main(int argc, char* argv[]) { int val = 12345; test_show_bytes(val); }

使用typedef定义一个指向unsigned char类型的指针类型别名,这个指针指向一块非负数的连续内存地址,show_bytes()函数打印指针指向的地址内容,%.2x表示以16进制输出

39 30 00 00 00 e4 40 46 f8 76 34 6f 01 00 00 00

show_int(), show_float(), show_pointer()是通过类型转换对show_bytes()函数进行封装,理解上述代码需要一定的C语言基础。sizeof()函数计算对象需要的字节数,使用sizeof进行计算,而不是写入固定值,是写可移植程序的基础。

变量12345的16进制表示为0x00003039,对于int类型,获取到相同的数值,只是字节序可能相反,本机0x39在最前面输出,说明是小端机器。浮点型数据对于同一个数值的编码是不同的

计算机系统中的地址和字节序(字节是计算机中)

数字编码

12345的浮点数表示为0x4640E400,小端表示顺序相反。