C语言中位域(bit fields)的可移植问题
网上有文章说C语言的“位域”(bit fields)有可移植性的问题,原因是不同的编译器对位域的实现不同。
我决定用实验验证一下。
一、 实验过程:
1. 准备实验程序
这 是谭浩强C语言课本上第12章12.2节的位域示例程序:
main() {
struct bs
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a = 1;
bit.b = 7;
bit.c = 15;
printf("%d,%d,%d\n", bit.a, bit.b, bit.c);
pbit = &bit;
pbit->a = 0;
pbit->b &= 3;
pbit->c |= 1;
printf("%d,%d,%d\n", pbit->a, pbit->b, pbit->c);
}
我将它改写成:
#include
int main(int argc, char** argv)
{
struct bitfields
{
unsigned long a:1;
unsigned long b:3;
unsigned long c:4;
unsigned long d:8;
unsigned long e:16;
unsigned long f:32;
};
union
{
struct bitfields bit;
unsigned long longhex;
unsigned long long longlonghex;
} union_bit;
union_bit.bit.a = 1;
union_bit.bit.b = 7;
union_bit.bit.c = 8;
union_bit.bit.d = 0x70;
union_bit.bit.e = 0x5060;
union_bit.bit.f = 0x10203040;
printf("a=%d b=%d c=%d d=0x%x\ne=0x%x f=0x%lx\n", union_bit.bit.a,
union_bit.bit.b, union_bit.bit.c, union_bit.bit.d, union_bit.bit.e, union_bit.bit.f);
printf("*(unsigned long*)(&bit) = %lx\n", union_bit.longhex);
printf("*(unsigned long long*)(&bit) = %llx\n", union_bit.longlonghex);
union_bit.bit.a = 0;
union_bit.bit.b = 3;
union_bit.bit.c = 9;
printf("a=%d b=%d c=%d d=0x%x\ne=0x%x f=0x%lx\n", union_bit.bit.a,
union_bit.bit.b, union_bit.bit.c, union_bit.bit.d, union_bit.bit.e, union_bit.bit.f);
printf("*(unsigned long*)(&bit) = %lx\n", union_bit.longhex);
printf("*(unsigned long long*)(&bit) = %llx\n", union_bit.longlonghex);
printf("sizeof unsigned long = %d\n", sizeof(unsigned long));
printf("sizeof struct bitfields = %d\n", sizeof(struct bitfields));
return 0;
}
2. 在不同的软硬件环境中运行实验程序,得到结果
1) 运行环境一:
硬件:1颗双核单线程的Pentium E5300, 主频2.60 GHz, 3 GB内存
软件:Fedora 12(内核2.6.31.5), gcc 4.4.2, glibc 2.11, 32位OS ,32位C编译器
运行结果:
a=1 b=7 c=8 d=0x70
e=0x5060 f=0x10203040
*(unsigned long*)(&bit) = 5060708f
*(unsigned long long*)(&bit) = 102030405060708f
a=0 b=3 c=9 d=0x70
e=0x5060 f=0x10203040
*(unsigned long*)(&bit) = 50607096
*(unsigned long long*)(&bit) = 1020304050607096
sizeof unsigned long = 4
sizeof struct bitfields = 8
2) 运行环境二:
硬件:1颗UltraSPARC T1, 主频1.0 GHz, 8核心×每核4线程, 64位32线程CPU, 8 GB内存
软件:Solaris 10 Update 3 for SPARC, 64位OS, 32位C编译器
运行结果:
a=1 b=7 c=8 d=0x70
e=0x5060 f=0x10203040
*(unsigned long*)(&bit) = f8705060
*(unsigned long long*)(&bit) = f870506010203040
a=0 b=3 c=9 d=0x70
e=0x5060 f=0x10203040
*(unsigned long*)(&bit) = 39705060
*(unsigned long long*)(&bit) = 3970506010203040
sizeof unsigned long = 4
sizeof struct bitfields = 8
3) 运行环境三:
硬件:1 颗双核单线程的Intel Xeon 3050芯片, CPU 主频为2.13 GHz, 配置8 GB内存
软件:FreeBSD 6.2, 64位OS, 64位C编译器
运行结果:
a=1 b=7 c=8 d=0x70
e=0x5060 f=0x7fff10203040
*(unsigned long*)(&bit) = 102030405060708f
*(unsigned long long*)(&bit) = 102030405060708f
a=0 b=3 c=9 d=0x70
e=0x5060 f=0x7fff10203040
*(unsigned long*)(&bit) = 1020304050607096
*(unsigned long long*)(&bit) = 1020304050607096
sizeof unsigned long = 8
sizeof struct bitfields = 8
二、 实验结果分析:
在32位x86系统上,位域对应的二进制位为:
ffffffff ffffffff ffffffff ffffffff eeeeeeee eeeeeeee dddddddd ccccbbba
因为long类型是32位,所以把整个bitfields作为unsigned long输出时,输出了整个bitfields的一部分:
eeeeeeee eeeeeeee dddddddd ccccbbba
在64位SPARC系统上,位域对应的二进制位为:
abbbcccc dddddddd eeeeeeee eeeeeeee ffffffff ffffffff ffffffff ffffffff
因为long类型是32位,所以把整个bitfields作为unsigned long输出时,也输出了整个bitfields的一部分:
abbbcccc dddddddd eeeeeeee eeeeeeee
在64位x86系统上,位域对应的二进制位为:
ffffffff ffffffff ffffffff ffffffff eeeeeeee eeeeeeee dddddddd ccccbbba
因为long类型是64位,在printf的时候"f=0x%lx"读取到了bitfields以外的内存,所以导致f=0x7fff10203040这样的结果。
并且,把整个bitfields作为unsigned long输出时,输出了整个bitfields的全部内容。
三、 实验结论:
1. 机器的字长和字节序,会直接影响到“位域”的值。
2. long类型,在64位编译器中是64位的数据类型;而在32位编译器中是32位数据类型。
3. long long 数据类型,在32位编译器和64位编译器中,都是64位类型。
注:关于字节序的说明:
大端字节(big endian)是指低地址存放最高有效位(MSB: Most Significant Bit);
小端字节(little endian)是低地址存放最低有效位(LSB: Least Significant Bit)。
用文字说明可能比较抽象,下面用图像加以说明。
比如数字0x0A0B0C0D在两种不同字节序CPU中的存储顺序如下所示:
Big Endian
低地址 ------> 高地址
+----+----+----+----+
| 0A | 0B | 0C | 0D |
+----+----+----+----+
Little Endian
低地址 ------> 高地址
+----+----+----+----+
| 0D | 0C | 0B | 0A |
+----+----+----+----+
Intel 80x86, MOS Technology 6502, Z80, VAX, PDP-11 处理器为 Little endian。
Motorola 6800, Motorola 68000, PowerPC 970, System/370, SPARC(除V9外) 处理器为 Big endian。
ARM, PowerPC (除PowerPC 970外), DEC Alpha, SPARC V9, MIPS, PA-RISC, Intel IA64 的字节序是可配置的。
为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但是,如果你的程序要跟别人的程序产生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而JAVA编写的程序则唯一采用big endian方式来存储数据。试想,如果你用C/C++语言在x86平台下编写的程序跟别人的JAVA程序互通时会产生什么结果?就拿上面的0x12345678来说,你的程序传递给别人的一个数据,将指向0x12345678的指针传给了JAVA程序,由于JAVA采取big endian方式存储数据,很自然的它会将你的数据翻译为0x78563412。什么?竟然变成另外一个数字了?是的,就是这种后果。因此,在你的C程序传给JAVA程序之前有必要进行字节序的转换工作。
无独有偶,所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。
相关文章
- C语言经典编程题100例 61~70
- C语言的整型溢出问题 int、long、long long取值范围 最大最小值「建议收藏」
- 动态规划之背包问题(C语言)
- C语言背包问题的算法(附完整源码)
- ACM 省赛E题 最长的递增子序列(动态规划+最长递增子序列)--------C语言—菜鸟级
- 城堡问题 (搜索+二进制)------------C语言—菜鸟级
- C语言中u8 u16 u32含义,有关stm32的问题,程序里面的u8、u16这些是什么意思啊「建议收藏」
- 「云顶书院」C语言复习笔记
- 【C语言】解决C语言题目中的多组输入问题
- Redis教程(十五):C语言连接操作代码实例
- C语言之printf函数详解编程语言
- C语言除法算法和取模运算的实现(多种算法,多种思路)
- Linux下如何使用C语言实现文件复制功能(linuxc复制文件)
- Linux下使用C语言编写文件:从入门到实践(linuxc写文件)
- Linux下如何高效调试C语言程序?(linux调试c语言程序)
- 之间的联系MySQL与C语言:搭建桥梁连接互动(mysql与c语言)
- 在Linux系统下学习C语言编程之旅(linux下编写c语言)
- C语言在Linux环境下的应用(linux c 后缀名)
- 程序Linux下使用C语言快速编写程序(linux怎么编写c)
- 如何有效处理C语言与MySQL的错误交互问题(c mysql错误处理)
- MySQL中BIT字段的默认值问题(mysql中bit默认值)
- MySQL中BIT类型的默认值问题(mysql中bit默认值)
- 使用C语言操作MySQL插入记录实例(c mysql插入记录)
- 乱码C语言Mysql如何解决中文乱码问题(c mysql存中文)
- C语言中MySQL增添新列的方法(c mysql增加一列)
- 解决Oracle中C语言乱码问题(c oracle乱码问题)
- 你必须知道的C语言预处理的问题详解
- 纯C语言:分治假币问题源码分享