转自:http://docs.oracle.com/cd/E19253-01/819-7054/chapter3-ex-10/index.html
使用 lint(1) 时,请记住,并非所有问题都将生成 lint(1) 警告,并且并非所有 lint(1) 警告都表示要求进行更改。请针对不同意图检查各种可能性。以下示例说明了转换代码时可能遇到的一些常见问题。在适当情况下,会显示对应的 lint(1) 警告。
由于类型为 int
的数据和指针在 ILP32 环境中长度相同,因此许多代码会依赖于这一假设。通常会将指针强制转换为 int
或 unsigned 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);
由于类型为 int
和 long
的数据在 ILP32 中从未真正加以区分,因此许多现有的代码会隐式或显式地假设它们可互换使用,并不加区分地进行使用。必须修改做出此假设的任何代码,使其可同时用于 ILP32 和 LP64。类型 int
和 long
在 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 位整数值之间大多数符号扩展问题的转换规则:
整型提升
无论有无符号,均可以在任何调用类型为 int
的表达式中使用 char
、short
、枚举类型或位字段。如果 int
可以支持初始类型的所有可能值,则值会转换为 int
类型。否则,值会转换为 unsigned int
类型。
带符号整数和无符号整数之间的转换
将带负号的整数提升为同一类型或更长类型的无符号整数时,该整数首先提升为更长类型的带符号相同值,然后再转换为无符号值。
有关转换规则的更详细讨论,请参阅 ANSI C 标准。该标准中还包括适用于普通运算转换和整数常量的规则。
以下示例编译为 64 位程序时,即使 addr
和 a.base
均是 unsigned
类型,addr
变量仍可成为带符号扩展变量。
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 -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#include int main(int argc, char *argv[]) { char *name = getlogin(); (void) printf("login = %s\n", name); return (0); }
在 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);