当前位置:编程学习 > 网站相关 >>

32位程序移植到64位平台前的准备工作

作者:启东编译 文章来源:天极

我们最近完成的一个项目是移植一个大型的32位应用程序,它可在64位环境中支持11个操作系统平台,并且程序的源代码超过了30万行。由于此32位程序是在几年前分成几部分开发而成,所以极有可能代码是由不同的开发者编写。鉴于此,我们有理由怀疑,在64位移植中导致问题的类型不匹配,很有可能是在这几年中随着程序模块的添加与删除而引入的。

  我们移植此32位程序到64位平台,是为了利用64位技术的先进之处--支持更大的文件、支持更大的内存、及64位计算,大体使用的方法是一个反复迭代的过程,不断地在一些细节问题上来来回回,如字节序、调整编译器选项等等,并时不时停下来查看是否达到了总体目标--遵从ANSI标准及源代码将来的可移植性。第一步,我们研究了64位的系统资源,以充分了解11个操作系统平台上每一个的编译器选项、内存模型和编码方面的考虑。作为全部工作的起点,我们在其中一个平台上打开了所有编译器警告,进行第一次构建,并仔细检查构建日志信息。通过这些最初的构建、使用本地调试器、及之后使用如Parasofts Insure++ (http://www.parasoft.com/)这样的工具,我们确定了一个开发蓝图,接下来,编制了一个清晰彻底的源代码目录清单,并在每次配置构建之后进行相应的检查。

  经过最初的代码修改、调试、查阅构建日志,已经有足够的信息对现实中可能碰到的事件进行排序以确定优先次序。在一个拥有所有基本功能的程序成功地通过我们的自动测试案例之后,移植工作总算到了一个转折点;此测试除了测试64位功能,也包含了向后兼容性测试。如果你所移植的项目中有几个不同的64位平台,很可能要在其上一一测试,一旦程序可在第一个平台上正确运行,接下来就要测试下一个平台,如此这般下去。然而,我们却发现了一个非常好的方法,可在同一时间,在所有的平台上进行工作,这是因为:

  ·每一个编译器都在它的警告信息中都提供了不同的信息,仔细查看几个编译器产生的错误,可有助于我们定位问题区域。

  ·不同平台上,错误也不同。同一个问题,在另一个平台上看上去无任何迹象,很可能会在当前平台上导致程序崩溃。

  在此项目中,最后需要考虑的一点是为最终发布的测试阶段作提前计划。因为最近修改的代码会被32位及64位的多平台共享,所以在每一个32位平台上,必须重新进行彻底地测试,这要花上双倍的测试时间和资源。

  跨平台问题

  期间还有许多的问题,在把32位程序移植到多个64位操作系统平台时,这些问题涵盖了从编译器警告到读写二进制数据等等。幸运的是,编译器能帮助我们确定大多数64位移植问题,可在所有平台上把编译器警告级别设为最严,要更多地关注那些指示出数据截断及把64位数据赋给32位数据的警告。不管怎样,把编译器警告设为更严的级别,将会导致多得数不清的警告信息,当然其中大部分能被编译器自身自动解决;此处最主要的问题是,很可能最重要的警告信息被大量的次要信息淹没了,没办法做出区分。为了解决这个问题,我们在不同平台上同时进行构建,因为不同的编译器能给出不同详细级别的警告信息,这将帮助我们做出区分,以过滤掉无用的信息,找出真正需要修正的问题所在。

  某些应用程序需要访问那些64位与32位共享的二进制数据或文件,在这种情况下,必须仔细检查long与指针的二进制格式,可能需要修改相关的读写函数以转换不同的大小,并在多平台间处理大字节序与小字节序问题。为得到正确的机器字节序,在64位程序中更大的数据尺寸需要更多的字节交换。

  例如,一个32位的long:

Big Endian(大字节序)= (B0, B1, B2, B3)

  转换为:

Little Endian(小字节序)= (B3, B2, B1, B0)

  而一个64位的long:

Big Endian(大字节序)= (B0, B1, B2, B3, B4, B5, B6, B7) 

  转换为:

Little Endian(小字节序)= (B7, B6, B5, B4, B3, B2, B1, B0)

  大多数的编译器能发现类型的不匹配,并能在构建期间纠正它们,这对如传递给函数的参数这样大多数的简单赋值来说是正确的。而真正的问题在于,对int、long、指针之间的不匹配,编译器在编译期间却视而不见,或者说编译器在编译期间所做的假设,导致了这种不匹配问题依然存在。前者涉及指针参数及函数指针,而后者主要是有关于函数原型。

  传递一个int和long指针作为函数参数时,如果这个指针之后被解引用为一个不同的、不兼容的类型,也会导致问题。这种情况不是32位代码的问题,而是因为ing与long是不可互换的。但是,在64位代码中,却因为指针与生具有的可伸缩性,这种情况将导致运行时错误,大多数编译器假定你正在做的事情,就是你想要做的事情,所以会悄无声息地对此问题放行,除非打开更多的警告信息。而通常只有在程序运行时,此问题才会浮出水面。

  举例来说,例1可同时在Solaris和AIX(Frote7、VAC 6)的32位与64位模式中通过编译,而没有任何警告,然而,64位的版本运行时却输出不正确的值。在如此短的示例中,这种问题很容易被发现,如果在更大型的代码中呢,恐怕会难多了吧。这种类型的问题可隐藏在现实中实际的代码里,而大多数的编译器却不能发现它们。

  例1:

#include <stdlib.h>
#include <stdio.h>

int Func1(char *);

int main()
{
 long arg, ret;
 arg = 247;
 ret = Func1((char *)&arg);
 printf("%ld ", ret);
 return(0);
}
int Func1(char * input)
{
 int *tmp;
 tmp = (int *)input;
 return(*tmp);
}

  当作为64位可执行程序运行于小字节序机器上时,例1显得一切正常,因为arg的值完全包含在long的四个最少有意义的字节中。然而,甚至在小字节序的x86机器上,当arg的值超出了四个最少有意义的字节时,64位的版本也会在运行期间产生一个错误。

  正是因为函数指针,编译器无法获取信息,以确定哪一个函数将会调用,因此它不能纠正或警告你有关可能存在的类型不匹配问题,所有通过特定函数指针调用的函数参数与返回类型都在此范围之内。如果想从根本上纠正此问题,你必须提供一种分离的方案,在函数调用时对参数及返回值进行适当的类型转换。

  第二个问题涉及隐式函数声明,如果没有对代码中调用的每一个函数提供一个原型,编译器就会做出假设。编译器产生的类似警告信息"Implicit function declaration: assuming extern returning int"在32位构建时通常是无关紧要的;但在64位构建时,这种返回值是int的假设,当函数实际上是返回long或指针(例如malloc)时,就会导致真正的问题了。为了不让编译器做出这种假设,必须确保所有需要的系统头文件都包含或提供了外部函数的函数原型。
隐藏的问题

  当然,也有一些问题不会在一开始就很容易地被发现,例如,在64位程序中,long与指针尺寸更大了,随之也会带来包含它们的结构大小上的增长。结构元素的排列方式决定了结构将占用多大的空间,举例来说,一个包含了int其后跟着一个long的结构,在32位程序中占用8字节,但64位程序在结构的第一个元素上加入了4个字节的填充数据,以使第二个元素在边界上排列得更自然,见图1:


图1:32位与64位结构中数据的排列方式

  为最小化填充数据所带来的影响,可把结构中的数据元素按从大到小重新排列。但是,如果数据元素是通过字节流访问的,还必须调整代码中的逻辑部分,以适应结构中的新的数据排列方式。

  在那些重新排列结构数据不起作用,或数据元素是通过字节流访问的情况中,就要小心计算填充数据,我们对此的解决方案是,实现一个帮助函数,在把数据写到字节流之前,从结构中消去多余的填充数据;而由此带来的另一个好处是,在读取数据时就不需要作任何修改了,参见例2:

  例2:

typdef struct demo{
 int i;
 long j;
} DEMO;

DEMO test;
/*pout_raw输出原始数据到一个文件中*/
/*输出结构的每一个元素并避免了填充数据*/
pout_raw ((int) file_unit, (char *) test.i, sizeof (test.i));
pout_raw ((int) file_unit, (char *) test.j, sizeof (test.j));

/*下行包含了填充数据*/
pout_raw ((int) file_unit, (char *) test,sizeof(test));

  数组问题

  64位上的long型数组或结构中的数组,不只是比它们32位的对等体包含更大的数值,而且可包含更多的元素。回头看一下前面用来定义数组边界和分配数组大小的4字节变量,它们可被转换成long。如果为了让64程序获得更好的性能,需决定现存的long数组是否应转换成int类型,请参考html">http://developers.sun.com/prodtech/cc/articles/ILP32toLP64Issues.html。
编码惯例与移植考虑

  除了遵循操作系统编译器文档所推荐的标准64位编码惯例之外,以下还有一些意见及小提示,也许在计划向64位移植时,可帮得上忙:

  ·如果可能且现实,把源代码转换为ANSI C/C++。这将简化64位移植过程,甚至将来的移植也会受益。

  ·你的目标操作系统同时支持32位与64位应用程序吗?应及早知道答案,因为它会对是否移植产生影响。例如:在Solaris上,使用系统命令isainfo检查32位与64位程序的兼容性。

% isainfo -v
64-bit sparcv9 applications
32-bit sparc applications

  ·如果你的源代码已经被如CVS之类的版本控制系统管理,在移植之前,将

补充:综合编程 , 安全编程 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,