本文主要是介绍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语言中位域(bit fields)的可移植问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!