Oracle® Developer Studio 12.5:C 用户指南

退出打印视图

更新时间: 2016 年 7 月
 
 

8.3 转换为 LP64 数据类型模型

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

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

8.3.2 整型和长型长度更改

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

此外,相对于 intunsigned int 数组,大型的 longunsigned long 数组可能会导致 LP64 数据类型模型出现严重的性能下降。大型的 longunsigned long 数组还可能会导致显著增加缓存未命中的情况,并占用更多的内存。

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

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

8.3.3 符号扩展

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

要了解发生符号扩展的原因,请考虑 ISO C 的转换规则。执行以下操作期间,导致 32 位和 64 位编译环境之间所发生大多数符号扩展问题的转换规则将生效:

  • 整型提升

    无论有无符号,均可在调用整型的任何表达式中使用 charshort枚举类型或位字段。

    如果一个整型可以容纳初始类型的所有可能值,则值转换为整型;否则,值转换为无符号整型数。

  • 带符号整型数和无符号整型数之间的转换

    当一个带负号的整数被提升为同一类型或更长类型的无符号整型数时,它首先被提升为更长类型的带符号等价值,然后转换为无符号值。

以下示例被编译为 64 位程序时,即使 addra.base 均是 unsigned 类型,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);
}

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

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

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

% cc -o test64 -m64 test.c
% ./test64
addr 0xffffffff80000000
addr 0x80000000
%

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

cc -o test -m32 test.c
%test

addr 0x80000000
addr 0x80000000

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

8.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;

8.3.5 结构

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

struct bar {
   int i;
   long j;
   int k;
   char *p;
};   /* sizeof (struct bar) = 32 */

以下示例显示在结构开头定义了 long 和指针数据类型的相同结构:

struct bar {
  char *p;
  long j;
  int i;
  int k;
};   /* sizeof (struct bar) = 24 */

8.3.6 联合

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

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

修改后的版本为:

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

8.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;

8.3.8 注意隐式声明

如果使用 -std=c90-xc99=none,C 编译器会假定在模块中使用却未在外部定义或声明的函数或变量为整型。编译器的隐式整型声明会将以此方式使用的任何 long 和指针数据截断。将函数或变量的相应 extern 声明置于头文件而非 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);
}

8.3.9 sizeof( ) 是无符号 long

在 LP64 数据类型模型中,sizeof() 的有效类型为 unsigned long。有时,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

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

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

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

确保 printf(3C)、sprintf(3C)、scanf(3C) 和 sscanf(3C) 的格式字符串可以容纳 long 或指针参数。对于指针参数,格式字符串中提供的转换操作应为 %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);

对于 long 参数,long 长度规范 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);