1. 大端序和小端序的简单说明:图中从左往右地址是逐渐变大的,如果是小端序存储(高地址放高位),读出来的就是 0x04030201;如果是大端序存储(高地址放低位),读出来就是 0x01020304。也就是同一块内存,按照不同字节序存储会产生两种值,大端序对应的值,小端序对应的值。大端序常用于网络传输,主要于网络传输的协议有关;小端序则常用于 CPU 的处理。
2. 判断当前运行环境是大端序还是小端序,可以利用联合体进行判别。思路就是,存入一个十六进制的数,看每个存储位置上的字节,进而进行大端序小端序的判断。比如存入 0x100(实际上就是 0x0100,在大端序环境下存储时显示为 01 00,小端序环境下显示为 00 01)。使用联合体的原因在于定义一个数,定义每个位置的字节,它俩占用的是同一块空间。例如联合体 union{short value; char b[2];} ,b[0] 就是 value 的第一个字节,b[1] 就是第二个字节,所以利用联合体判断非常自然
#include <stdio.h>
// 如果是大端序就返回1,如果是小端序就返回0
int IsBigEndian(){
union {
char b[2];
short s;
} value={.s=0x100};
return value.b[0] == 1;
}
int main(){
printf("%#x", IsBigEndian());
return 0;
}
3. 字节序转换,大端序转小端序,小端序转大端序;不同平台字节序不同,所以在处理数据时需要字节序转换操作
#include <stdio.h>
// 字节序转换,大端序会转换成小端序,小端序会转换成大端序,因为大端序和小端序存储位置相反,所以只要让字节存储位置变相反就行了
int ToggleEndian(int original){
union{
char b[4]; // 传进来的是四个字节的int
int value;
} endian={.value=original};
char temp = endian.b[0];
endian.b[0] = endian.b[3];
endian.b[3] = temp;
temp = endian.b[1];
endian.b[1] = endian.b[2];
endian.b[2] = temp;
return endian.value;
}
int main(){
int x = 0x12345678;
printf("%#x\n", x); // 0x12345678
printf("%#x ", ToggleEndian(x)); // 0x78563412
return 0;
}
这里的逻辑稍微有点绕,首先我机器是小端序存储,存储 0x12345678 时内存中保存的应该是 78 56 34 12,但是读出来的值是 0x12345678,那怎么证明字节序经过转换了呢?如果内存中保存的变为了 12 34 56 78,那么编译器打印出来的内容应该是 0x78563412,这就可以说明字节序转换成功了。
4. 用联合体判断字节序的初衷在于可以定义字节数组对同一空间的变量进行字节的判断,但是忽略了指针这一强大的工具,不必需要字节数组,指针直接去获取变量所在的空间,然后进行判断也是可以的。提供指针版本的判断字节序代码,这里注意一下,同一个 x 经过两次字节序转换函数后的值相同,因为传入的参数 original 没有被修改,修改的是系统复制后的 original,可以调试观看
#include <stdio.h>
// 字节序转换,大端序会转换成小端序,小端序会转换成大端序,因为大端序和小端序存储位置相反,所以只要让字节存储位置变相反就行了
int ToggleEndian(int original){
union{
char b[4]; // 传进来的是四个字节的int
int value;
} endian={.value=original};
char temp = endian.b[0];
endian.b[0] = endian.b[3];
endian.b[3] = temp;
temp = endian.b[1];
endian.b[1] = endian.b[2];
endian.b[2] = temp;
return endian.value;
}
int ToggleEndianPointer(int original){ // 这里传入original参数的时候,系统就已经复制了一份
char *p = (char *)&original; // 所以这里的地址是复制后的original的地址,不是传入的original的地址
char temp = p[0];
p[0] = p[3];
p[3] = temp;
temp = p[1];
p[1] = p[2];
p[2] = temp;
return original; // 这里有个点需要注意一下,这里面修改original并不会改变外界的值,因为传的参数不是原始的original的地址
}
int main(){
int x = 0x12345678;
printf("%#x\n", x); // 0x12345678
printf("%#x\n", ToggleEndian(x)); // 0x78563412
printf("%#x\n", ToggleEndianPointer(x)); // 0x78563412
return 0;
}
5. 发现了一个有趣的事情,指针强转类型,单纯的强转类型不会改变大小,但是在取值的时候,大小就发生变化了;也就是说,强转指针类型的目的在于取值的时候取几位。细细想来,其实其他类型也是如此,一个 double 类型的变量强转为 int,不就是为了取 4 个字节的数吗,指针只是绕了个弯子,先强转指针类型然后再取值,而不是直接强转原先变量的类型
char buffer[8];
printf("%d\n", sizeof(buffer)); // 8
printf("%d\n", sizeof( (char *)buffer) ); // 8
printf("%d\n", sizeof( (short *)buffer) ); // 8
printf("%d\n", sizeof( (int *)buffer) ); // 8
printf("%d\n", sizeof( *((char *)buffer) ) ); // 1
printf("%d\n", sizeof( *((short *)buffer) ) ); // 2
printf("%d\n", sizeof( *((int *)buffer) ) ); // 4
double a = 2;
printf("%d\n", sizeof(a)); // 8
printf("%d\n", sizeof((int)a)); // 4