在计算机中,内存地址的顺序通常从低到高排列,即从左到右为升序。这意味着较低的内存地址在左边,较高的内存地址在右边。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//1.通过十六进制得到本机是大端还是小端
//2.输出十六进制的每一位

typedef unsigned char* byte_pointer;

void show_bytes(byte_pointer start, size_t len) {
    size_t i;
    for (i = 0; i < len; i++) {
	printf("%.2x", start[i]); // %.2x表示输出两位16进制数(x表示忽略0x)
				  // %.2x是确保每个字节都是两位的十六进制数
    }
    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 ival = val;
    float fval = (float) ival;
    int* pval = &ival;
    show_int(ival);
    show_float(fval);
    show_pointer(pval);
}

int main() {
    test_show_bytes(197521); //0x30391,由输出可以得知本机是小端
    int val = 0x87654321;
    byte_pointer valp = (byte_pointer) &val;
    show_bytes(valp, 1);
    show_bytes(valp, 2);
    show_bytes(valp, 3);
    return 0;
}


大小端(Endian)是计算机系统中用于描述多字节数据存储顺序的术语。它们决定了计算机如何以字节顺序存储和读取数据,特别是在处理多字节的数据类型(如 intfloat 等)时。大小端主要分为两种:小端(Little Endian)和大端(Big Endian)。

小端(Little Endian)

在小端序中,数据的最低有效字节(Least Significant Byte,LSB)存储在内存的最低地址处。换句话说,字节序列从最低有效字节到最高有效字节依次存储。这种顺序在某些处理器架构中是默认的,如 x 86 架构。

示例:

假设我们有一个 32 位整数 0x12345678,它在小端序中的存储方式如下:

地址 内容
0 x 00 0 x 78
0 x 01 0 x 56
0 x 02 0 x 34
0 x 03 0 x 12

在这里,最低地址 0x00 存储的是最低有效字节 0x78,而最高地址 0x03 存储的是最高有效字节 0x12

大端(Big Endian)

在大端序中,数据的最高有效字节(Most Significant Byte,MSB)存储在内存的最低地址处。即,字节序列从最高有效字节到最低有效字节依次存储。这种顺序在一些网络协议和某些处理器架构(如 IBM 的 POWER 系列)中使用。

示例:

同样的整数 0x12345678 在大端序中的存储方式如下:

地址 内容
0 x 00 0 x 12
0 x 01 0 x 34
0 x 02 0 x 56
0 x 03 0 x 78

在大端序中,最低地址 0x00 存储的是最高有效字节 0x12,而最高地址 0x03 存储的是最低有效字节 0x78

举例说明

假设有一个数组 unsigned char arr[4] 用来存储一个 32 位整数,值为 0x12345678。在小端序和大端序系统中,内存布局将如下:

小端序:

arr[0] = 0x78; // LSB
arr[1] = 0x56;
arr[2] = 0x34;
arr[3] = 0x12; // MSB

大端序:

arr[0] = 0x12; // MSB
arr[1] = 0x34;
arr[2] = 0x56;
arr[3] = 0x78; // LSB

例子

在提供的 C 代码中,您正在使用数组“a”并使用不同的方法来访问和修改其元素。以下是代码功能的细分:

#include <stdio.h>
#include <stdlib.h>

int main()
{
int a[3]; // 声明一个包含 3 个整数的数组
a[0] = 0; // 将 0 分配给第一个元素
*(a + 1) = 1; // 使用指针算法将 1 分配给第二个元素
*((char*)a + 4) = 2; // 将 2 分配给 a + 4 字节处的内存位置

printf("%d %d %d\n", a[0], a[1], a[2]); // 打印数组元素的值
return 0;
}

解释:

  1. 数组声明int a[3]; 声明一个包含三个整数的数组 a

  2. 元素访问和修改

    • a[0] = 0; 将数组的第一个元素设置为 0。
    • *(a + 1) = 1; 使用指针运算将数组的第二个元素设置为 1。表达式 a + 1 相当于 a[1] 的地址,使用 * 取消引用它允许您分配该值。
    • *((char*)a + 4) = 2; 将内存位置 a + 4 字节的值设置为 2。此行将指针 a 转换为 char*,从而将指针运算更改为字节级而不是整数级。由于 int 通常占用 4 个字节(取决于系统架构),a + 1 通常引用下一个整数。但是,转换为 char* 并添加 4 个字节会访问第二个整数元素的第二个字节。

这行代码比较棘手,因为它可能会修改整数的一部分,而不是直接设置整数值。根据系统的字节顺序(它如何存储字节顺序),这可能会导致意外行为。

  1. 打印值printf 函数输出存储在 a[0]a[1]a[2] 中的值。

潜在问题:

  • 对齐和系统依赖性*((char*)a + 4) = 2; 行不可移植,因为它取决于系统如何处理整数大小和对齐。它可能会导致不同系统或架构上的行为不同。
  • 字节序*((char*)a + 4) = 2; 的效果取决于系统的字节序。在小端系统中,它会修改 a[1] 的最低有效字节。在大端系统中,它会修改整数的不同部分。(==这一步操作还取决于系统的大小端==)

为避免这些问题,最好使用标准方式访问和修改数组元素,除非您有特定原因需要直接使用字节并且您了解其含义。

重要性和应用

  1. 网络通信: 许多网络协议(如 TCP/IP)采用大端序,称为“网络字节序”。这意味着在跨平台通信时,数据需要在主机字节序和网络字节序之间转换。

  2. 跨平台数据交换: 不同的计算机架构可能采用不同的字节序,在进行文件读写或数据传输时需要考虑这一点,以确保数据的一致性。

  3. 系统设计和调试: 理解大小端的概念有助于开发者在处理低级别数据时避免错误,特别是在涉及到内存解析或直接操作字节时。

总结

大小端只是数据在内存中存储的一种方式。理解这些概念有助于确保数据在不同系统和平台之间的一致性和正确性。