Sun Studio 12:C 用户指南

7.3 转换为 LP64 数据类型模型

以下示例说明在转换代码时可能遇到的某些常见问题。适当时会显示相应的 lint 警告。

7.3.1 整型和指针长度更改

由于整型和指针在 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);

7.3.2 整型和长型长度更改

由于整型和长型在 ILP32 数据类型模型中从未真正加以区分,因此现有代码可能会不加区分地使用它们。修改交换使用整型和长型的任何代码,使其可同时符合 ILP32 和 LP64 数据类型模型的要求。整型和长型在 ILP32 数据类型模型中均为 32 位,而长型在 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

此外,与 int 或 unsigned int 数组相比,大型整型数组(如 long 或 unsigned long)可能会导致 LP64 数据类型模型中的性能显著下降。大型的 long 或 unsigned long 数组还可能会导致显著增加缓存未命中的情况,并占用更多的内存。

因此,如果对于应用程序而言 int 与 long 的效果一样好,最好使用 int,而不要使用 long。

也是出于这种原因,使用 int 数组而不要使用指针数组。某些 C 应用程序在转换为 LP64 数据类型模型后出现显著的性能下降,这是因为它们依赖于很多较大的指针数组。

7.3.3 符号扩展

转换到 64 位编译环境时,经常会遇到符号扩展问题,这是因为类型转换和提升规则有些模糊。为防止出现符号扩展问题,请使用显式强制类型转换以取得预期结果。

要了解出现符号扩展的原因,了解 ISO C 的转换规则会有所帮助。可能会导致 32 位和 64 位编译环境之间大多数符号扩展问题的转换规则在以下操作过程中有效:

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


%cat 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);
}

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


% cc -o test64 -xarch=v9 test.c
% ./test64
addr 0xffffffff80000000
addr 0x80000000
%

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


cc -o test test.c
%test

addr 0x80000000
addr 0x80000000

有关转换规则的详细讨论,请参见 ISO C 标准。此标准中还包含对普通算术转换和整型常量有用的规则。

7.3.4 指针运算而不是整数

通常,由于指针运算独立于数据模型,而整数不可以,因此使用指针运算比整数好。此外,通常可以使用指针运算简化代码。请看以下示例:


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;

7.3.5 结构

检查应用程序中的内部数据结构有无漏洞。在结构中的字段之间使用额外填充,以满足对齐要求。对于 LP64 数据类型模型,当长型或指针字段增至 64 位时,会分配此额外填充。在 SPARC 平台上的 64 位编译环境中,所有类型的结构均与结构中最长成员的长度对齐。当您重组结构时,请遵循将长型和指针字段移到结构开头的简单规则。考虑以下结构定义:


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 */

7.3.6 联合

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


typedef union {
   double _d;
   long _l[2];
} llx_t;

下面是修改的版本


typedef union {
   double _d;
   int _l[2];
} llx_t;

7.3.7 类型常量

在某些常量表达式中,缺少精度会导致数据丢失。请在常量表达式中显式指定数据类型。通过增加 {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;

7.3.8 注意隐式声明

如果使用 -xc99=none,C 编译器会假定在模块中使用却未在外部定义或声明的函数或变量为整型。编译器的隐式整型声明会将以此方式使用的任何长型和指针截断。将函数或变量的相应外部声明置于头文件而非 C 模块中。在使用函数或变量的 C 模块中包含此头文件。如果它是系统头文件定义的函数或变量,您还需要在代码中包含正确的头文件。请看以下示例:


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);
}

7.3.9 sizeof( ) 是无符号 long

在 LP64 数据类型模型中,sizeof() 的有效类型为无符号长型。有时,sizeof() 会传递给需要使用类型为 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

7.3.10 使用强制类型转换显示您的意图

关系表达式可能会因为转换规则而显得错综复杂。您应该通过在必要的地方增加强制类型转换很明确地指定表达式的求值方式。

7.3.11 检查格式字符串转换操作

确保 printf(3S)、sprintf(3S)、scanf(3S) 和 sscanf(3S) 的格式字符串可以容纳长型或指针参数。对于指针参数,格式字符串中提供的转换操作应为 %p,以便能在 32 位和 64 位编译环境中运行。


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);

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


size_t nbytes;
u_long 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;
u_long align, addr, raddr, alloc;
printf("kalloca:%lu%%%lu from heap got%lx.%lx returns%lx\n",
nbytes, align, raddr, raddr + alloc, addr);