Solaris(64 位)开发者指南

第 4 章 转换应用程序

对于应用程序开发者而言,需要面对以下两个有关转换的基本问题:

尝试维护包含尽可能少的 #ifdefs 的单一源代码通常比维护多个源代码树更好。本章将指导您编写在 32 位和 64 位环境中均能够正常工作的代码。最理想的情况是,对当前代码的转换只需重新编译以及重新链接到 64 位的库。然而,还有需要更改代码的情况,本章讨论了有助于使这类转换更容易的工具。

数据模型

如前所述,32 位环境和 64 位环境之间最大的区别在于两种基础数据类型的变化。

用于 32 位应用程序的 C 数据类型模型是 ILP32 模型,之所以这样命名,是因为类型 intlong 和指针均为 32 位。LP64 数据模型是用于 64 位应用程序的 C 数据类型模型,业界公司联盟已就此达成一致。之所以这样命名,是因为 long 和指针类型的数据长度会增加到 64 位。其余的 C 类型 intshortchar 均与 ILP32 模型中的相应类型相同。

以下的程序样例 foo.c 直接对比说明了 LP64 数据模型与 ILP32 数据模型的不同结果。同一个程序既可以编译为 32 位程序,也可以编译为 64 位程序。

#include <stdio.h>

int

main(int argc, char *argv[])

{

		(void) printf("char is \t\t%lu bytes\n", sizeof (char));

		(void) printf("short is \t%lu bytes\n", sizeof (short));

		(void) printf("int is \t\t%lu bytes\n", sizeof (int));

		(void) printf("long is \t\t%lu bytes\n", sizeof (long));

		(void) printf("long long is \t\t%lu bytes\n", sizeof (long long));

		(void) printf("pointer is \t%lu bytes\n", sizeof (void *));

		return (0);

} 

32 位编译的结果是:


% cc -O -o foo32 foo.c

% foo32 

char is          1 bytes

short is         2 bytes

int is           4 bytes

long is          4 bytes

long long is     8 bytes

pointer is       4 bytes   

64 位编译的结果是:


% cc -xarch=generic64 -O -o foo64 foo.c 

% foo64

char is          1 bytes

short is         2 bytes

int is           4 bytes

long is          8 bytes

long long is     8 bytes 

pointer is       8 bytes

注 –

缺省编译环境旨在最大化可移植性,即创建 32 位应用程序。


C 整型之间的标准关系仍然适用。

sizeof (char) <= sizeof (short) <= sizeof (int) <= sizeof (long)

表 4–1 列出了基本 C 数据类型及其在 LP32 和 LP64 数据类型模型中的对应长度(以位为单位)。

表 4–1 数据类型的长度(以位为单位)

C 数据类型 

ILP32 

LP64 

char

不变 

short

16 

不变 

int

32 

不变 

long

32 

64

long long

64 

不变 

pointer

32 

64

enum

32 

不变 

float

32 

不变 

double

64 

不变 

long double

128 

不变 

某些旧的 32 位应用程序可互换使用 intlong 和指针类型。类型为 long 的数据和指针长度在 LP64 数据模型中会增加。需要注意的是,单是这种变化就可导致许多 32 位到 64 位的转换问题。

此外,声明和强制类型转换在说明意图时也会变得非常重要。类型更改时,表达式的求值方式会受到影响。标准 C 转换规则的作用受数据类型长度变化的影响。要充分说明意图,可能需要声明常量的类型。为了确保按照预期的方式对表达式求值,可能还需要在表达式中进行强制类型转换。对表达式进行正确的求值在符号扩展时尤其重要,在这种情况下进行显式强制类型转换可能是实现预期效果所必需的。

对于内置的 C 运算符、格式字符串、汇编语言以及兼容性和互操作性,还会出现其他问题。

本章的其余部分将通过介绍以下内容来给出解决这些问题的建议:

实现单一源代码

以下几节介绍了一些可供应用程序开发者使用的资源,可帮助编写能够同时支持 32 位编译和 64 位编译的单一源代码。

系统头文件 <sys/types.h><inttypes.h> 中包含有助于使应用程序对于 32 位和 64 位均安全的常量、宏和派生类型。尽管详细讨论这些内容超出了本文档的范围,但是以下各节以及附录 A,派生类型更改 中仍讨论了其中的部分内容。

功能测试宏

包含 <sys/types.h> 的应用程序源文件可以通过包括 <sys/isa_defs.h> 使编程模型符号 _LP64_ILP32 的定义可用。

有关预处理程序符号(_LP64_ILP32)和宏(_LITTLE_ENDIAN_BIG_ENDIAN6)的信息,请参见 types(3HEAD)。

派生类型

使用系统派生类型有助于使代码对于 32 位和 64 位均安全,这是由于派生类型本身对于 ILP32 和 LP64 数据模型均安全。通常,使用派生类型以便于更改是良好的编程做法。如果数据模型在将来发生变化,或者在移植到其他平台时,只需更改系统派生类型即可,而无需更改应用程序。

<sys/types.h> 文件

<sys/types.h> 头文件中包含大量应在适当时机使用的基本派生类型。特别是以下几种类型颇受关注:

clock_t

类型 clock_t 表示系统时间(以时钟周期为单位)。

dev_t

类型 dev_t 用于设备号。

off_t

类型 off_t 用于文件大小和偏移量。

ptrdiff_t

类型 ptrdiff_t 是一种带符号整数类型,用于对两个指针执行减法运算后所得的结果。

size_t

类型 size_t 用于内存中对象的大小(以字节为单位)。

ssize_t

带符号的大小类型 ssize_t 供返回字节计数或错误指示的函数使用。

time_t

类型 time_t 用于时间(以秒为单位)。

所有这些类型在 ILP32 编译环境中都保持 32 位值,并会在 LP64 编译环境中增加到 64 位值。

其中某些类型的用法将在本章转换为 LP64 的指导原则中更详细地介绍。

<inttypes.h> 文件

在 Solaris 2.6 发行版中添加了头文件 <inttypes.h>,程序员可利用它提供的常量、宏和派生类型使其代码与显式指定大小的数据项兼容,而不管编译环境如何。该文件中包含用来处理 8 位、16 位、32 位和 64 位对象的机制,它是 ANSI C 议案的一部分,可以与 ISO/JTC1/SC22/WG14 C 委员会对当前 ISO C 标准(即 ISO/IEC 9899:1990 编程语言-C)所进行修订的工作草案保持一致。

<inttypes.h> 提供的基本功能包括:

以下各节中将对这些功能进行更详细的讨论。

定宽的整数类型

<inttypes.h> 提供的定宽整数类型同时包括带符号整数类型(如 int8_tint16_tint32_tint64_t)以及无符号整数类型(如 uint8_tuint16_tuint32_tuint64_t)。定义为可具有指定位数的最短整数类型的派生类型包括 int_least8_tint_least64_tuint_least8_tuint_least64_t

不应不加选择地使用这些定宽类型。例如,类型 int 可以继续用于循环计数器和文件描述符,类型 long 可用于数组索引。另一方面,对于以下各项的显式二进制表示形式,则应使用定宽类型:

uintptr_t 和其他有用的类型

<inttypes.h> 提供的其他有用类型包括大小足以包含一个指针的带符号整数类型和无符号整数类型。这些类型以 intptr_tuintptr_t 形式提供。此外,还会将 intmax_tuintmax_t 分别定义为可用的最长(以位为单位)带符号整数类型和无符号整数类型。

选用 uintptr_t 类型作为指针的整数类型比使用基本类型(如 unsigned long)要好。尽管在 ILP32 和 LP64 数据模型中,类型 unsigned long 与指针的长度相同,但如果使用 uintptr_t,则只需在使用其他数据模型时更改 uintptr_t 的定义即可。这会使其可移植到许多其他系统中,并且还可以在 C 中更为清楚地表达意图。

需要执行地址运算时,intptr_tuintptr_t 类型对于强制转换指针非常有用。因此,应使用这些类型,而不是使用 longunsigned long 类型。


注 –

使用 uintptr_t 进行强制类型转换通常比使用 intptr_t 安全,在进行比较时尤为安全。


常量宏

提供宏的目的在于指定给定常量的大小和符号。这些宏包括 INT8_C(c)INT64_C(c)UINT8_C(c)UINT64_C(c) 等。基本上,这些宏会在常量的末尾放置一个 lulllull(如有必要)。例如,对于 ILP32,INT64_C(1) 会在常量 1 后面附加 ll;对于 LP64,则附加 l

用来使常量成为最长类型的宏包括 INTMAX_C(c)UINTMAX_C(c)。这些宏对于指定转换为 LP64 的指导原则中介绍的常量类型会非常有用。

<inttypes.h> 定义的限制

<inttypes.h> 定义的限制是用于为各种整数类型指定最小值和最大值的常量,其中包括每个定宽类型的最小值(INT8_MININT64_MIN 等)和最大值(如 INT8_MAXINT64_MAX 等)及其对应的无符号的最小值和最大值。

该文件还提供了每个最短长度类型的最小值和最大值,其中包括 INT_LEAST8_MININT_LEAST64_MININT_LEAST8_MAXINT_LEAST64_MAX 等及其对应的无符号的最小值和最大值。

最后,该文件定义了支持的最长整数类型的最小值和最大值,其中包括 INTMAX_MININTMAX_MAX 及其对应的无符号的最小值和最大值。

格式字符串宏

<inttypes.h> 文件还提供了用于指定 printfscanf 格式说明符的宏。实际上,这些宏会根据内置于宏名称中的参数的位数,在格式说明符前面放置一个 lll,将参数指定为 longlong long 类型。

printf(3C) 的宏以十进制、八进制、无符号和十六进制格式列显 8 位、16 位、32 位和 64 位整数以及最短和最长整数类型。例如,以十六进制表示法列显 64 位整数:

int64_t i;

printf("i =%" PRIx64 "\n", i);

同样,scanf(3C) 的宏也以十进制、八进制、无符号和十六进制格式读取 8 位、16 位、32 位和 64 位整数以及最长整数类型。例如,读取无符号的 64 位十进制整数:

uint64_t u;

scanf("%" SCNu64 "\n", &u);

请勿毫无限制地使用这些宏,最好将其与定宽类型一起使用。有关更多详细信息,请参阅定宽的整数类型一节。

工具支持

Sun Studio 10 编译器中提供的 lint 程序可以检测潜在的 64 位问题,并有助于使代码对于 64 位安全。此外,该 C 编译器的 -v 选项也会非常有用。该选项可指示编译器执行更严格的附加语义检查,还会针对指定的文件启用某些与 lint 相似的检查。

有关 C 编译器和 lint 的功能的更多信息,请参见《Sun Studio 10: C User's Guide》。

用于 32 位和 64 位环境的 lint

lint 既可用于 32 位代码又可用于 64 位代码。 对于要在 32 位和 64 位环境中运行的代码,请使用 -errchk=longptr64 选项。-errchk=longptr64 选项用于检查是否可将代码移植到长整数和指针的长度为 64 位、无格式整数的长度为 32 位的环境中。

对于要在 64 位 SPARC 环境中运行的 lint 代码,应当使用 -Xarch=v9 选项。将 -errchk=longptr64 选项和 -Xarch=v9 选项一起使用,可以针对要在 64 位 SPARC 上运行的代码生成有关潜在 64 位程序问题的警告。

从 Solaris 10 发行版开始,对于要在 64 位 AMD 环境中运行的 lint 代码,应当使用 -Xarch=amd64 选项。


注 –

lint-D__sparcv9 选项已不再需要,不应当再使用。


有关 lint 选项的说明,请参见《Sun Studio 10: C User's Guide》。

生成警告时,lint(1) 会列显违例代码的行号、描述问题的警告消息以及是否涉及指针的注释。它还会指明所涉及类型的长度。如果确定涉及指针并且知道数据类型的长度,则会便于查找特定的 64 位问题,并避免 32 位和更短类型之间的已有问题。


注 –

尽管 lint 会给出有关潜在的 64 位问题的警告,但也无法检测所有的问题。请务必牢记,在 lint 生成的警告中,并非所有警告都是真正的 64 位问题。在许多情况下,符合应用程序意图并且正确无误的代码也会生成警告。


以下的程序和 lint(1) 输出样例说明了非纯 64 位代码中可能出现的大多数 lint 警告。

    1	#include <inttypes.h>

    2	#include <stdio.h>

    3	#include <stdlib.h>

    4	

    5	static char chararray[] = "abcdefghijklmnopqrstuvwxyz";

    6	

    7	static char *myfunc(int i)

    8	{	

    9		return(& chararray[i]);

   10	}

   11	

   12	void main(void)

   13	{

   14		int	intx;

   15		long	longx;

   16		char	*ptrx;

   17	

   18		(void) scanf("%d", &longx);

   19		intx = longx;

   20		ptrx = myfunc(longx);

   21		(void) printf("%d\n", longx);

   22		intx = ptrx;

   23		ptrx = intx;

   24		intx = (int)longx;

   25		ptrx = (char *)intx;

   26		intx = 2147483648L;

   27		intx = (int) 2147483648L;

   28		ptrx = myfunc(2147483648L);

   29	}



(19) warning: assignment of 64-bit integer to 32-bit integer

(20) warning: passing 64-bit integer arg, expecting 32-bit integer: myfunc(arg 1)

(22) warning: improper pointer/integer combination: op "="

(22) warning: conversion of pointer loses bits

(23) warning: improper pointer/integer combination: op "="

(23) warning: cast to pointer from 32-bit integer

(24) warning: cast from 64-bit integer to 32-bit integer

(25) warning: cast to pointer from 32-bit integer

(26) warning: 64-bit constant truncated to 32 bits by assignment

(27) warning: cast from 64-bit integer constant expression to 32-bit integer

(28) warning: passing 64-bit integer constant arg, expecting 32-bit integer: myfunc(arg 1)

function argument ( number ) type inconsistent with format

    scanf (arg 2) 	long * :: (format) int *	t.c(18)

    printf (arg 2) 	long  :: (format) int 	t.c(21)

(仅当常量表达式不适合它所强制转换到的类型时,才会发出从以上代码样例的第 27 行中生成的 lint 警告。)

通过在上一行中加一个 /*LINTED*/ 注释,可以禁止对给定的源代码行发出警告。如果确实要让代码以特定方式运行(例如强制类型转换和赋值)时,这种做法会很有用。使用 /*LINTED*/ 注释时请务必谨慎,因为它可能会掩盖真实问题。有关更多信息,请参阅《Sun Studio 10: C User's Guide》或 lint(1) 手册页。

转换为 LP64 的指导原则

使用 lint(1) 时,请记住,并非所有问题都将生成 lint(1) 警告,并且并非所有 lint(1) 警告都表示要求进行更改。请针对不同意图检查各种可能性。以下示例说明了转换代码时可能遇到的一些常见问题。在适当情况下,会显示对应的 lint(1) 警告。

请勿假设 int 和指针的长度相同

由于类型为 int 的数据和指针在 ILP32 环境中长度相同,因此许多代码会依赖于这一假设。通常会将指针强制转换为 intunsigned int 类型以进行地址运算。但是,由于类型为 long 的数据和指针在 ILP32和 LP64 数据类型模型中长度均相同,因此可以将指针强制转换为 long 类型。请使用类型 uintptr_t 而不是显式使用 unsigned long,因为前者可更贴切地表达意图并使代码具有更强的可移植性,从而使其不会受到将来变化的影响。例如,

char *p;

p = (char *) ((int)p & PAGEOFFSET);

将生成以下警告:

warning: conversion of pointer loses bits

使用以下代码将生成不带警告的结果:

char *p;

p = (char *) ((uintptr_t)p & PAGEOFFSET);

请勿假设 intlong 的长度相同

由于类型为 intlong 的数据在 ILP32 中从未真正加以区分,因此许多现有的代码会隐式或显式地假设它们可互换使用,并不加区分地进行使用。必须修改做出此假设的任何代码,使其可同时用于 ILP32 和 LP64。类型 intlong 在 ILP32 数据类型模型中均为 32 位,而类型 long 在 LP64 数据模型中则为 64 位。例如,

int waiting;

long w_io;

long w_swap;

...

waiting = w_io + w_swap;

将生成以下警告:

warning: assignment of 64-bit integer to 32-bit integer

符号扩展

转换到 64 位时,经常会遇到非故意符号扩展问题。该问题很难在实际发生前检测到,因为 lint(1) 不会针对它发出警告。此外,类型转换和提升规则也有些模糊。要解决非故意符号扩展的问题,必须使用显式强制类型转换来实现预期结果。

要了解出现符号扩展的原因,了解 ANSI C 的转换规则会有所帮助。以下是可能会导致 32 位和 64 位整数值之间大多数符号扩展问题的转换规则:

  1. 整型提升

    无论有无符号,均可以在任何调用类型为 int 的表达式中使用 charshort、枚举类型或位字段。如果 int 可以支持初始类型的所有可能值,则值会转换为 int 类型。否则,值会转换为 unsigned int 类型。

  2. 带符号整数和无符号整数之间的转换

    将带负号的整数提升为同一类型或更长类型的无符号整数时,该整数首先提升为更长类型的带符号相同值,然后再转换为无符号值。

有关转换规则的更详细讨论,请参阅 ANSI C 标准。该标准中还包括适用于普通运算转换和整数常量的规则。

以下示例编译为 64 位程序时,即使 addra.base 均是 unsigned 类型,addr 变量仍可成为带符号扩展变量。


示例 4–1 test.c

struct foo {

		unsigned int	base:19, rehash:13;  

};



main(int argc, char *argv[]) 

{

		struct foo	a;

		unsigned long addr;



		a.base = 0x40000;

		addr = a.base << 13;		/* Sign extension here! */

		printf("addr 0x%lx\n", addr);



		addr = (unsigned int)(a.base << 13);  /* No sign extension here! */

		printf("addr 0x%lx\n", addr);

}

进行此符号扩展的原因是按以下方式应用了转换规则:

  1. a.base 由于整型提升规则而从类型 unsigned int 转换为 int。因此,表达式 a.base << 13 的类型为 int,但是未进行符号扩展。

  2. 表达式 a.base << 13 的类型为 int,但是在赋值给 addr 之前,由于带符号和无符号整型提升规则,会转换为类型 long,然后再转换为类型 unsigned long。从类型 int 转换为 long 时,会进行符号扩展。


% cc -o test64 -xarch=v9 test.c

% ./test64

addr 0xffffffff80000000

addr 0x80000000

%

如果将同一示例编译为 32 位程序,则不显示任何符号扩展:


% cc -o test32 test.c

% ./test32

addr 0x80000000

addr 0x80000000

%

使用指针运算而不是地址运算

通常,由于指针运算是独立于数据模型的,而地址运算则可能不是,因此使用指针运算比地址运算更好。此外,使用指针运算通常还可以简化代码。例如,

int *end;

int *p;

p = malloc(4 * NUM_ELEMENTS);

end = (int *)((unsigned int)p + 4 * NUM_ELEMENTS);

将生成以下警告:

warning: conversion of pointer loses bits

以下代码将生成不带警告的结果:

int *end; 

int *p;

p = malloc(sizeof (*p) * NUM_ELEMENTS);

end = p + NUM_ELEMENTS;

对结构重新压缩

由于 long 和指针字段在 LP64 中会增加到 64 位,因此编译器可能会向结构中添加额外的填充内容,以满足对齐要求。对于 SPARCV9 ABI 和 amd64 ABI,所有类型的结构均与结构中最长成员的长度对齐。对结构重新压缩时,一个简单的规则就是,将 long 和指针字段移到结构开头,然后重新排列其余的字段,通常(但不总是)按长度的降序排列,具体取决于这些字段可以压缩的程度。例如,

struct bar {

		int i;

		long j;

		int k;

		char *p;

};			/* sizeof (struct bar) = 32 */

要获取更好的结果,请使用:

struct bar {

		char *p;

		long j;

		int i;

		int k;

};			/* sizeof (struct bar) = 24 */

注 –

基本类型的对齐方式在 i386 和 amd64 ABI 之间会发生变化。请参见对齐问题


检查联合类型

请确保对联合类型进行检查,因为其字段的长度在 ILP32 和 LP64 数据类型模型之间可能会发生变化。例如,

typedef union {

       double   _d;

       long _l[2];

} llx_t;

应当为:

typedef union {

       double _d;

       int _l[2];

} llx_t;

指定常量类型

在某些常量表达式中,由于精度不够而会导致数据丢失。这些类型的问题很难发现。请在常量表达式中显式指定数据类型。通过向每个整型常量的末尾增加 {u,U,l,L} 的某些组合,可以指定其类型。另外,也可以使用强制类型转换来指定常量表达式的类型。例如,

int i = 32;

long j = 1 << i;		/* j will get 0 because RHS is integer expression */

应当为:

int i = 32;

long j = 1L << i;

注意隐式声明

在某些编译模式下,编译器可能会假设针对在模块中使用却未在外部定义或声明的任何函数或变量,使用 int 类型。编译器的隐式 int 类型声明会将以此方式使用的任何类型为 long 的数据和指针截断。函数或变量的相应 extern 类型声明应置于头文件而非 C 模块中。然后,使用此函数或变量的任何 C 模块中应包含此头文件。如果是系统头文件定义的函数或变量,则仍然需要在代码中包含正确的头文件。

例如,由于未声明 getlogin(),因此以下代码:

int

main(int argc, char *argv[])

{

		char *name = getlogin()

		printf("login = %s\n", name);

		return (0);

}

将生成以下警告:

warning: improper pointer/integer combination: op "="

warning: cast to pointer from 32-bit integer

implicitly declared to return int 

getlogin        printf   

要获取更好的结果,请使用:

#include <unistd.h>

#include <stdio.h>

 

int

main(int argc, char *argv[])

{

		char *name = getlogin();

		(void) printf("login = %s\n", name);

		return (0);

}

sizeofunsigned long

在 LP64 环境中,sizeof 的有效类型为 size_t,该类型会实现为 unsigned long。有时,sizeof 会传递给需要使用类型为 int 的参数的函数,或者赋值给 int 类型的数据或强制转换为 int 类型。在某些情况下,这种截断可能会导致数据丢失。例如,

long a[50];

unsigned char size = sizeof (a);

将生成以下警告:

warning: 64-bit constant truncated to 8 bits by assignment

warning: initializer does not fit or is out of range: 0x190

使用强制类型转换说明意图

关系表达式可能会因转换规则而变得错综复杂。您应当通过在必要的地方添加强制类型转换来非常明确地指定表达式的求值方式。

检查格式字符串转换操作

对于类型为 long 或指针的参数,可能需要更改 printf(3C)sprintf(3C)scanf(3C)sscanf(3C) 的格式字符串。对于要同时在 32 位和 64 位环境中运行的指针参数,格式字符串中给定的转换操作应为 %p。例如,

char *buf;

struct dev_info *devi;

...

(void) sprintf(buf, "di%x", (void *)devi);

将生成以下警告:

warning: function argument (number) type inconsistent with format

  sprintf (arg 3)     void *: (format) int

使用以下代码将生成不带警告的结果:

char *buf;

struct dev_info *devi;

...

(void) sprintf(buf, "di%p", (void *)devi);

另外,请检查以确保 buf 指向的存储器大小足以包含 16 个数字。对于类型为 long 的参数,long 类型的长度规范 l 应置于格式字符串中转换操作字符的前面。例如,

    size_t nbytes;

    ulong_t align, addr, raddr, alloc;

    printf("kalloca:%d%%%d from heap got %x.%x returns %x\n", 

            nbytes, align, (int)raddr, (int)(raddr + alloc), (int)addr);

将生成以下警告:

warning: cast of 64-bit integer to 32-bit integer

warning: cast of 64-bit integer to 32-bit integer

warning: cast of 64-bit integer to 32-bit integer

以下代码将生成不带警告的结果:

    size_t nbytes;

    ulong_t align, addr, raddr, alloc;

    printf("kalloca:%lu%%%lu from heap got %lx.%lx returns %lx\n", 

            nbytes, align, raddr, raddr + alloc, addr);

其他注意事项

其余指导原则将重点说明将应用程序转换为完全 64 位程序时遇到的常见问题。

长度增加的派生类型

许多派生类型已进行了更改,以便在 64 位应用程序环境中表示 64 位值。此更改不会影响 32 位应用程序;但是,使用或导出这些类型所描述的数据的任何 64 位应用程序均需要重新求值以确保正确。关于这一点的一个示例是直接处理 utmpx(4) 文件的应用程序。要在 64 位 应用程序环境中正确操作,请勿尝试直接访问这些文件,而应当使用 getutxent(3C) 及相关的函数系列。

附录 A,派生类型更改中包括了更改的派生类型的列表。

检查更改的副作用

需要注意的一个问题是,一个区域中的类型更改可能会导致另一个区域中进行意外的 64 位转换。例如,如果某个函数以前返回类型 int 而现在返回 ssize_t,则需要检查所有调用方。

检查直接使用 long 类型是否仍有意义

由于类型 long 在 ILP32 模型中为 32 位,在 LP64 模型中为 64 位,因此可能会出现以前定义为 long 类型的数据既不恰当又不必要的情况。在这种情况下,请尽可能使用可移植性更强的派生类型。

与此相关的是,由于上述原因,许多派生类型在 LP64 数据类型模型中可能已更改。例如,pid_t 在 32 位环境中仍为 long 类型,但是在 64 位环境中,pid_t 则为 int 类型。有关针对 LP64 编译环境修改的派生类型的列表,请参见附录 A,派生类型更改

对显式 32 位与 64 位原型使用 #ifdef

在某些情况下,一个接口存在特定的 32 位和 64 位版本是不可避免的。可以通过在头文件中使用 _LP64_ILP32 功能测试宏来区分这些版本。同样,要在 32 位和 64 位环境中运行的代码也需要利用相应的 #ifdefs,具体取决于编译模式。

算法更改

在确认代码对 64 位环境是安全的之后,请再次检查代码,以验证算法和数据结构是否仍有意义。由于数据类型变大,因此数据结构可能会使用更多空间。代码的性能也可能变化。考虑到以上几点,您可能需要相应地修改代码。

入门清单

假设需要将代码转换为 64 位,以下清单可能会有所帮助:

程序样例

以下程序样例 foo.c 直接对比说明了 LP64 数据模型与 ILP32 数据模型的不同结果。同一个程序既可以编译为 32 位程序,也可以编译为 64 位程序。

#include <stdio.h>

int

main(int argc, char *argv[])

{

		(void) printf("char is \t\t%lu bytes\n", sizeof (char));

		(void) printf("short is \t%lu bytes\n", sizeof (short));

		(void) printf("int is \t\t%lu bytes\n", sizeof (int));

		(void) printf("long is \t\t%lu bytes\n", sizeof (long));

		(void) printf("long long is \t\t%lu bytes\n", sizeof (long long));

		(void) printf("pointer is \t%lu bytes\n", sizeof (void *));

		return (0);

} 

32 位编译的结果是:


% cc -O -o foo32 foo.c

% foo32 

char is          1 bytes

short is         2 bytes

int is           4 bytes

long is          4 bytes

long long is     8 bytes

pointer is       4 bytes   

64 位编译的结果是:


% cc -xarch=generic64 -O -o foo64 foo.c 

% foo64

char is          1 bytes

short is         2 bytes

int is           4 bytes

long is          8 bytes

long long is     8 bytes 

pointer is       8 bytes

注 –

缺省编译环境旨在最大化可移植性,即创建 32 位应用程序。