Linux GCC 64位编程技巧

  1. 云栖社区>
  2. 博客>
  3. 正文

Linux GCC 64位编程技巧

华仔爱技术 2010-10-17 19:48:00 浏览1357
展开阅读全文

                                 linux GCC 64位编程技巧

64位系统的优势?

既然要采用64位系统,首先要知道64位系统的优势所在。对于技术人员来说,完全没有必要去看那些厂家拿出的厚厚的说明书、或者某个研究机构抛出的一堆的数字,64位系统的优势总结起来很简单:内存大、速度快

内存大

32位系统相比,64位系统的地址空间大大增大,达到了18PB18PB究竟是多大呢?说出来有点吓人:4G内存的40亿倍!这么大的空间,不要说内存了,就是整个磁盘的数据都放进去也是没有任何问题的。

需要注意的是:已有的32位系统由于采用了物理地址扩展技术(PAEhttp://en.wikipedia.org/wiki/Physical_Address_Extension ),使得操作系统可用物理内存能够超过4G,但对于单个程序来说,能够使用的内存(即地址空间)还是只有4G,这个是无法突破的。

速度快

你可能很容易就推断如下结论:64位系统速度应该比32位快,而且应该是快两倍!

但实际情况可能让你有点吃惊:64位和速度没有绝对的比例关系,甚至可能速度更低。

64位架构处理器就本身而言,不会在速度上两倍于32位的同等处理器。为什么呢?简单来说:所谓的64位,只是和内存地址空间有关,和CPU速度没有一点关系,更不是64/322倍的关系。

另外,如果程序不需要对大量的数据进行处理,64位反而可能变慢,因为64位的地址是8位,32位的地址是4位,如果处理器的缓存是一样大小,那么很明显64位处理器能够缓存的东西是32位的一半,这反而降低了缓存命中率,因此影响了性能。

 

既然这样,那我们为什么还要说64位会更快呢?

我们知道,决定性能的不单是CPU,还有内存、磁盘、网络等一大堆东东,而64位机器在性能上改善最大的就是内存容量大大增加,从4G扩展到18PB,这就意味着内存中可以放很多数据,避免频繁的磁盘读写IO,从而大大提高性能;同时也意味着同一时间可以处理更多的数据,这也能够大大提高性能,尤其是多核多CPU并行处理的时候。

CPU本身结构的变化也会带来性能上的提升,这个提升主要体现在处理超过32位的整形数据上。对于32位系统,为了处理超过32位的整形数字,需要4次额外的寄存器操作;而64位系统,不需要这4次额外的寄存器操作。

某些CPU可能会为64位运算提供一些特定的特性,这也会带来一些特定处理上的性能提升,不过这种提升和具体的CPU相关,不是通用的特性。相关信息可以参考:更快、更强 64位编程的三十二条军规

什么情况下一定要使用64位?

64位虽好,但并不是放之四海皆准,毕竟从已有的32位升级到64位是要银子的,如果你写个“Hello, world!”也一定要求放到64位系统上来提高性能,那完全是浪费!

如下情况是必须用到64位系统的情况:

1.      程序(不是整个系统)需要超过4G的内存地址空间;

2.      使用libkvm库,或者/dev/mem/dev/kmem的文件;

3.      使用/proc调试64位进程;

4.      使用只有64位版本的库;

5.      需要64位寄存器来更高效的进行64位运算,例如处理long long类型数据;

6.      使用超过2G的文件;

 

32位和64位开发环境差别?

如果你是使用Java/Python等跨平台的语言进行开发,那么恭喜你,所谓的64位和32位对你来说没有差别,因为底层的虚拟机已经屏蔽掉了这种差异,在语言的层面是不需要理解这种差异的(这也是跨平台的一个原因吧)。

但如果你是用C/C++,那就有点郁闷了:C/C++是和系统强相关的,你在32位机器上写的代码,拿到64位机器上运行,可能出现你意料之外的结果,甚至可能崩溃,而且你还很难定位!

 

问题虽然很严重,而原因很简单:32位系统使用的数据模型是ILP32,而64位系统使用的数据模型是LP64或者LLP64.

ILP32:指的是int, long, pointer长度是32位,取首字母合起来就是ILP32(下面的简写都是这样的)windowsUnix32位系统都是这种模型;

LP64:指的是long, pointer64位,这个是Unix64位系统采用的数据模型;

LLP64:指的是long long, pointer64位,而long还是32位,这是Windows64位系统采用的模型;

 

除了这个最主要的区别外,另外一个区别就是有的库可能只有64位版本,但不会只有32位版本,因为64位是支持所有32位的库的。

Linux GCC 64位编程技巧

1  长整形数据

既然64位和32位开发环境主要的差别是数据模型的不同,那么最简单的一个方法就是尽力避开这种差异:咱们不用long类型了,管你32位还是64位,惹不起还躲不起么?

如果一定要用长整形,也还是不要用long,直接用__int64_t,如果你觉得写起来麻烦,那就自己定义为LLONGtypedef  __int64_t LLONG即可

2  指针数据

long类型可以不用,但指针没有办法不用,那就只能勇敢的面对了:)

1.指针打印:在使用printf的时候,指针打印控制符是%p,不要用%d或者%i.

2.指针转换:将指针转换为整形数据的时候,使用intptr_t,不要使用int

 

如果用C++stream流,则不存在这些问题,直接输出即可。

3  使用sizeof来确定长度

不管任何时候,都使用sizeof来确定变量或者类型的长度。

例如:对于结构来说,64位系统默认对齐的长度是8字节,而32位默认是4字节,因此在使用结构长度的时候,不能将长度写死,而要使用sizeof计算。

4  注意常量类型陷阱

当你写下long  j = 1 << 32;的时候,你是否以为系统会将1识别为long类型,然后给j赋值为232次方?

然而系统却在这里设下了一个陷阱:如果你不指定常量类型的话,常量默认就是int类型的

因此不管你是32位还是64位,j的值都是0

 

要想避开这个陷阱,就要指定常量的类型(常量也是有类型的),因此要写成:long j = 1L << 32;

常见常量类型:l/Lll/LLul/ULull/ULL

5  注意sign extension陷阱

当负整数转换为更长类型的无符号整数时,首先是转换为目标类型对应的有符号类型,然后再转换为无符号类型。

例如:

int i = -1;

unsigned long j = i;

-1首先转换为signed long型,再转换为unsigned long.

 

在这个转换过程中,有一个小小的陷阱:当从短的数据类型转到长的数据类型的时候,为了保证负数的有效性,会在多于的比特位填充1(因为负数的二进制码是补码,只有补1才能保证负数的符号和取值都不会变).

例如:

int i = 0x80000000; //对应十进制的有符号数-2147483648或者无符号数2147483648

unsigned long j = i;

你可能以为j会直接等于0x0000000080000000,但事实上j会等于0xffffffff800000000;

6  size_tpid_t***_t类型

C/C++的库为了支持跨32位和64位,很多函数返回值都是***_t,例如sizeof操作,这种***_t实际上就是随32位或64位而变化的整形数。

1.      如果要使用返回值为***_t的函数,则变量也直接声明为***_t;

2.      如果要打印***_t的变量,gcc可以使用%Zu, %Zd;或者直接将变量转换为更长类型的数据后再打印,例如long或者long long,使用%lu, %llu, %ld, %lld控制符进行控制。

7  如果你不清楚长度,那么就转换为一个最长的数据

如果有的类型,如果你根本不知道它到底有多长,或者到底是什么类型,那么最简单的方式就是将其转换为一个最长的数据:整形当然是long long了,如果你连是否有符号都不知道,那就转换为double吧。

8  打开所有编译开关,关注警告

无论在32位还是64位系统下编译,-Wall选项都打开,虽然这不能保证100%能够发现所有问题,哪怕只能解决一个,也能够帮助你大大减少自己定位问题的时间。

参考资料

64位编程军规:

http://www.vipcn.com/chengxukaifa/shujujiegou/gengkuaigengqiang-64weibianchengdesanshiertiaojungui.html

 

sign extension

http://en.wikipedia.org/wiki/Sign_extension

 

RHEL5相关数据类型以及长度一览表:

网友评论

登录后评论
0/500
评论
华仔爱技术
+ 关注