
在嵌入式C语言中,大小端(Endianness)是一个重要的概念,它涉及到多字节数据在内存中的存储顺序。
大小端,又称端序(Endianness),是指多字节数据在内存中的存储顺序。对于单字节数据,不存在存储顺序的问题,因为每个字节都是独立的。但是,对于多字节数据(如int、float、double等),就需要考虑字节的存储顺序了。

int num = 0x12345678,在大端模式下,在内存中的存储顺序为:| 内存地址(低 -> 高)| 存储内容 |
|----|----|
|0x00|0x12|
|0x01|0x34|
|0x02|0x56|
|0x03|0x78|int num = 0x12345678,在小端模式下,在内存中的存储顺序为:| 内存地址(低 -> 高)| 存储内容 |
|----|----|
|0x00|0x78|
|0x01|0x56|
|0x02|0x34|
|0x03|0x12|大小端与硬件体系的关系十分密切,不同的处理器和平台可能会采用不同的大小端方式。
在嵌入式C语言中,可以通过编写程序来判断当前系统的大小端方式。以下是一个简单的判断方法。
联合体是一种特殊的数据结构,它允许在相同的内存位置存储不同类型的数据。利用联合体的这一特性,可以方便地判断系统的大小端方式。
#include <stdio.h>
int main() {
union {
unsigned int i;
unsigned char c[sizeof(unsigned int)];
} test_union;
test_union.i = 0x12345678; // 设定一个已知的整数值
if (test_union.c[0] == 0x78) {
printf("The system is Little Endian!\n");
} else {
printf("The system is Big Endian!\n");
}
return 0;
}
定义了一个联合体test_union,它包含一个无符号整型i和一个字符数组c。我们将一个已知的整数值0x12345678赋给i,然后检查c[0]的值。如果c[0]等于0x78,说明系统的最低有效字节存储在最低的内存地址处,即系统是小端序;否则,系统是大端序。
另一种常用的方法是使用指针强制类型转换。我们可以将一个整数的地址转换为字符指针,然后检查该指针所指向的第一个字节的值。
#include <stdio.h>
#include <stdint.h>
int main() {
uint32_t x = 0x12345678;
char *c = (char*)&x;
if (*c == 0x78) {
printf("This system is little-endian.\n");
} else if (*c == 0x12) {
printf("This system is big-endian.\n");
} else {
printf("Unable to determine endianness.\n");
}
return 0;
}
我们创建了一个32位的无符号整数x,并将其地址转换为一个字符指针c。然后,我们检查*c的值来判断系统的大小端方式。
可以通过预处理器指令来定义一个宏,在编译时根据不同的大小端模式进行不同的操作。示例代码如下:
#include <stdio.h>
#if defined(__BIG_ENDIAN__)
#define ENDIAN "大端"
#elif defined(__LITTLE_ENDIAN__)
#define ENDIAN "小端"
#else
#define ENDIAN "未知"
#endif
int main() {
printf("当前系统是 %s 模式\n", ENDIAN);
return 0;
}在某些平台上,可以利用标准库函数来判断系统的大小端方式。例如,在POSIX兼容的系统上,可以使用htons(Host TO Network Short)和ntohl(Network TO Host Long)等函数来检查系统的字节序。然而,这种方法依赖于特定的平台和库,因此并不是所有系统都适用。
在嵌入式系统开发、网络编程等场景中,当涉及不同大小端模式设备之间的数据交互时,就需要进行大小端转换。
通过手动位操作来交换字节顺序,这是最直接的方法。对于16位、32位和64位的数据类型,可以分别编写转换函数。
#include <stdio.h>
#include <stdint.h>
// 16位数据大小端转换
uint16_t swap_endian_16(uint16_t x) {
return (x >> 8) | (x << 8);
}
// 32位数据大小端转换
uint32_t swap_endian_32(uint32_t x) {
return ((x >> 24) & 0x000000FF) |
((x >> 8) & 0x0000FF00) |
((x << 8) & 0x00FF0000) |
((x << 24) & 0xFF000000);
}
// 64位数据大小端转换
uint64_t swap_endian_64(uint64_t x) {
return ((x >> 56) & 0x00000000000000FFULL) |
((x >> 40) & 0x000000000000FF00ULL) |
((x >> 24) & 0x0000000000FF0000ULL) |
((x >> 8) & 0x00000000FF000000ULL) |
((x << 8) & 0x000000FF00000000ULL) |
((x << 24) & 0x0000FF0000000000ULL) |
((x << 40) & 0x00FF000000000000ULL) |
((x << 56) & 0xFF00000000000000ULL);
}
int main() {
// 测试16位数据转换
uint16_t num16 = 0x1234;
uint16_t swapped16 = swap_endian_16(num16);
printf("16位数据: 原始值 0x%04x, 转换后 0x%04x\n", num16, swapped16);
// 测试32位数据转换
uint32_t num32 = 0x12345678;
uint32_t swapped32 = swap_endian_32(num32);
printf("32位数据: 原始值 0x%08x, 转换后 0x%08x\n", num32, swapped32);
// 测试64位数据转换
uint64_t num64 = 0x123456789ABCDEF0ULL;
uint64_t swapped64 = swap_endian_64(num64);
printf("64位数据: 原始值 0x%016llx, 转换后 0x%016llx\n", num64, swapped64);
return 0;
}
swap_endian_16、swap_endian_32 和 swap_endian_64 三个函数分别用于对 16 位、32 位和 64 位无符号整数进行大小端转换。
使用 %04x、%08x 和 %016llx 格式化输出 16 位、32 位和 64 位的十六进制数,确保输出的结果长度符合预期。
联合体允许在同一内存位置存储不同类型的数据。通过联合体,可以方便地访问和修改数据的不同字节。
#include <stdint.h>
#include <stdio.h>
// 使用联合体实现32位数据的大小端转换
uint32_t swap_endian_32_union(uint32_t x) {
union {
uint32_t i;
uint8_t c[4];
} u;
u.i = x;
uint32_t swapped = (u.c[0] << 24) | (u.c[1] << 16) | (u.c[2] << 8) | u.c[3];
return swapped;
}
int main() {
// 初始化一个32位无符号整数用于测试
uint32_t num = 0x12345678;
// 调用大小端转换函数
uint32_t swapped_num = swap_endian_32_union(num);
// 输出原始值和转换后的值
printf("原始的32位数据: 0x%08x\n", num);
printf("转换后的32位数据: 0x%08x\n", swapped_num);
return 0;
}
swap_endian_32_union 函数:定义了一个匿名联合体,该联合体包含两个成员:一个 32 位无符号整数 i 和一个包含 4 个 8 位无符号整数的数组 c。由于联合体的所有成员共享同一块内存空间,所以可以通过不同的方式来访问这块内存。
x 赋值给联合体的 i 成员。c 中的每个字节重新组合成一个新的 32 位无符号整数 swapped,实现了大小端的转换。通过指针和数组也可以实现大小端转换,这种方法与联合体类似,但更加直接地操作内存。
#include <stdint.h>
#include <stdio.h>
// 使用指针实现32位数据的大小端转换
uint32_t swap_endian_32_pointer(uint32_t x) {
uint8_t *bytes = (uint8_t *)&x;
uint32_t swapped = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
return swapped;
}
int main() {
// 定义一个32位无符号整数用于测试
uint32_t original_num = 0x12345678;
// 调用大小端转换函数
uint32_t swapped_num = swap_endian_32_pointer(original_num);
// 输出原始值和转换后的值
printf("原始32位数据: 0x%08x\n", original_num);
printf("转换后32位数据: 0x%08x\n", swapped_num);
return 0;
}
swap_endian_32_pointer 函数:接收一个 uint32_t 类型的参数 x,表示需要进行大小端转换的 32 位无符号整数。
x 的地址强制转换为 uint8_t * 类型的指针 bytes,这样就可以将 32 位整数按字节访问。bytes 数组中的每个字节重新组合成一个新的 32 位无符号整数 swapped,实现了大小端的转换。 在某些编译器和系统库中,提供了专门用于大小端转换的函数。例如,在 GCC 编译器中,有htons、htonl、ntohs和ntohl等函数,主要用于网络编程中主机字节序和网络字节序(大端序)之间的转换:
htons:将 16 位无符号整数从主机字节序转换为网络字节序。htonl:将 32 位无符号整数从主机字节序转换为网络字节序。ntohs:将 16 位无符号整数从网络字节序转换为主机字节序。ntohl:将 32 位无符号整数从网络字节序转换为主机字节序。#include <stdio.h>
#include <arpa/inet.h>
int main() {
uint16_t num16 = 0x1234;
uint32_t num32 = 0x12345678;
uint16_t swapped16 = htons(num16);
uint32_t swapped32 = htonl(num32);
printf("16位原始数据: 0x%x, 转换后: 0x%x\n", num16, swapped16);
printf("32位原始数据: 0x%x, 转换后: 0x%x\n", num32, swapped32);
return 0;
}使用宏定义可以更方便地实现大小端转换,尤其是在需要频繁进行转换的代码中。以下是 32 位数据转换的宏定义示例:
#include <stdio.h>
#include <stdint.h>
#define SWAP32(x) \
((((x) & 0xFF000000) >> 24) | \
(((x) & 0x00FF0000) >> 8) | \
(((x) & 0x0000FF00) << 8) | \
(((x) & 0x000000FF) << 24))
int main() {
uint32_t num = 0x12345678;
uint32_t swapped = SWAP32(num);
printf("原始数据: 0x%x, 转换后: 0x%x\n", num, swapped);
return 0;
}
在嵌入式系统中,大小端的影响主要体现在以下几个方面:
int、float 等)进行存储和读取时,要考虑大小端的影响。例如,在小端模式下,int num = 0x12345678,实际在内存中存储的顺序是 0x78、0x56、0x34、0x12。如果按照大端模式的思维去读取,就会得到错误的数据。int 类型转换为 char 数组时,在小端模式下,低字节先存储,转换后数组中的元素顺序与大端模式下是相反的。htonl、htons、ntohl、ntohs 等函数,以确保数据在不同环境下的正确传输和处理。struct {
char a;
int b;
short c;
} myStruct;在小端模式下,假设 char 占 1 字节,short 占 2 字节,int 占 4 字节,且内存对齐为 4 字节,那么 myStruct 的成员在内存中的布局可能是 a 占 1 字节,然后填充 3 字节达到 4 字节对齐,接着是 b 的 4 字节,最后是 c 的 2 字节。而在大端模式下,布局可能不同。
union {
uint32_t i;
uint8_t c[4];
} u;
u.i = 0x12345678;
if (u.c[0] == 0x78) {
printf("小端模式\n");
} else {
printf("大端模式\n");
}#if defined(__BIG_ENDIAN__)
// 大端模式下的代码
#elif defined(__LITTLE_ENDIAN__)
// 小端模式下的代码
#else
#error "未知的字节序"
#endif综上所述,大小端是嵌入式C语言中一个重要的概念,它涉及到多字节数据在内存中的存储顺序。了解并掌握大小端的概念和判断方法对于嵌入式系统的开发和调试具有重要意义。