この節では実際の例を使用して、コードを変換したときに発生する可能性のある一般的な問題をいくつか紹介します。対応する lint の警告がある場合は、その警告も示します。
ILP32 コンパイル環境では整数とポインタは同じサイズであるため、コードには、この前提に立って作成されているものがあります。アドレス演算では、ポインタはしばしば int または unsigned int に型変換されます。LP64 コンパイル環境への変換では、ポインタは long に型変換してください。これは、ILP32 と LP64 データ型モデルで、long とポインタが同じサイズであるためです。明示的に unsigned long を使用するのではなく、unintptr_t を使用してください。uintptr_t の方が目的の用途により近く、コードの移植性を高めるため、将来的に変更しなくてもよいようにします。次の例を考えてみましょう。
char *p; p = (char *) ((int)p & PAGEOFFSET); % 警告: ポインタの変換でビットが失われます
char *p; p = (char *) ((uintptr_t)p & PAGEOFFSET);
ILP32 データ型モデルでは実際には整数とロング整数が区別されないため、ほとんどの場合、既存のコードでは区別なしに整数とロング整数が使用されています。整数とロング整数が区別なしに使用されているコードは、修正して ILP32 と LP64 両方のデータ型モデルの条件に準拠するようにしてください。ILP32 データ型モデルでは整数とロング整数はともに 32 ビットですが、LP64 データ型モデルではロング整数は 64 ビットです。次の例を考えてみましょう。
int waiting; long w_io; long w_swap; ... waiting = w_io + w_swap; % 警告: 64 ビット整数を 32 ビット整数に代入します
型の変換と拡張規則はいくぶん曖昧ですから、64 ビットコンパイル環境への移行で、符号の拡張はよく問題になります。符号の拡張の問題を避けるには、明示的な型変換を使用して、意図した結果を得られるようにしてください。
符号の拡張が発生する理由を理解するには、ANSI C の変換規則の知識が役立ちます。32 ビットと 64 ビットコンパイル環境間で最大の符号拡張問題を引き起こすと思われる変換規則は、次の処理で適用されます。
整数の拡張
整数を必要とする式では、符号の有無に関係なく、char、short、enumerated type、ビットフィールドを使用することができます。
整数が元の型が取り得る値をすべて保持できる場合、値は整数に変換され、それ以外の場合は、符号なし整数に変換されます。
符号付きと符号なし整数間の変換
負符号付きの整数を同じまたは大きい型の符号なし整数に拡張する場合は、最初に大きな型符号付き整数に拡張され、次に符号なし値に変換されます。
次のコードを 64 ビットプログラムとしてコンパイルすると、addr と a.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; /* ここで符号拡張する ! */ printf("addr 0x%lx¥n", addr); addr = (unsigned int)(a.base << 13); /* 符号拡張しない ! */ printf("addr 0x%lx¥n", addr); }
ここで符号拡張が起きるのは、次のように変換規則が適用されるためです。
a.base は、整数拡張規則により符号なし 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 ビットプログラムとしてコンパイルすると、符号拡張はまったく表示されません。
変換規則の詳細については、ANSI C 規格の仕様書を参照してください。この規格には通常の演算変換や整数定数に関する有用な規則も規定されています。
ポインタ演算が常にデータモデルから独立しているのに対し、アドレス演算は独立していないことがあるため、一般的にはアドレス演算を使用するより、ポインタ演算を使用する方がよいでしょう。また、通常、ポインタ演算を使用することによって、コードを簡単にすることもできます。次の例を考えてみましょう。
int *end; int *p; p = malloc(4 * NUM_ELEMENTS); end = (int *)((unsigned int)p + 4 * NUM_ELEMENTS); % 警告: ポインタの変換でビットが失われます
int *end; int *p; p = malloc(sizeof (*p) * NUM_ELEMENTS); end = p + NUM_ELEMENTS;
アプリケーションの内部データ構造体に穴がないか検査してください。境界整列条件を満たすには、構造体のフィールドとフィールドの間にパディングをします。このパディングは、long または pointer フィールドが LP64 データ型モデル用に 64 ビットになったときに適用します。SPARC プラットフォームの 64 ビットコンパイル環境では、あらゆる種類の構造体が、その中の最大量のサイズに合わせて整列されます。構造体を整列し直すときは、long および pointer フィールドを構造体の先頭に移動するという簡単な規則に従ってください。次の例を考えてみましょう。
struct bar { int i; long j; int k; char *p; }; /* sizeof (struct bar) = 32 */
次は、同じ構造体の例です。long および pointer データ型を構造体の先頭で定義しています。
struct bar { char *p; long j; int i; int k; }; /* sizeof (struct bar) = 24 */
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; /* RHS が整数式のため j は 0 になる */
int i = 32; long j = 1L << i;
C コンパイラは、モジュールで使用されていて、外部定義または宣言されてない関数や変数をすべて整数とみなします。このようにして使用されるロング整数やポインタは、コンパイラの暗黙の整数宣言によって切り捨てられます。この問題を避けるには、C モジュールではなく、ヘッダーに関数または変数に対する適切な extern 宣言を挿入してください。そして、その関数または変数を使用する C モジュールにヘッダーをインクルードしてください。システムヘッダーによって定義されている関数あるいは変数であっても、コードに正しいヘッダーをインクルードする必要があります。次の例を考えてみましょう。
int main(int argc, char *argv[]) { char *name = getlogin() printf("login = %s¥n", name); return (0); } % 警告: ポインタ/整数の組み合わせは不適切です: 演算子 "=" 警告: 32 ビット整数からポインタにキャストしています 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); }
LP64 データ型モデルでは、sizeof() の有効な型は unsigned long です。sizeof() は、ときには int 型の引数を待つ関数に渡されたり、整数に代入あるいは型変換されたりします。そうした場合は、切り捨てによってデータが失われることがあります。
long a[50]; unsigned char size = sizeof (a); % 警告: 代入によって 62 ビット定数が 8 ビットに切り捨てられました 警告: 初期設定子が適合していないか範囲を超えています: 0x190
変換規則により、関係式は扱いにくいことがあります。必要に応じて型変換を追加することによって、式の評価方法を明示するようにしてください。
printf(3S)、sprintf(3S)、scanf(3S)、sscanf(3S) に対する書式文字列が long あるいは pointer 引数を受け付けられるようになっていることを確認してください。pointer 引数については、書式文字列中の変換操作を %p で指定して、32 ビットおよび 64 ビット両方のコンパイル環境で機能するようにします。
char *buf; struct dev_info *devi; ... (void) sprintf(buf, "di%x", (void *)devi); % 警告: 関数への引数の型と初期とが整合していません 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); % 警告: 64 ビット整数から 32 ビット整数にキャストしています 警告: 64 ビット整数から 32 ビット整数にキャストしています 警告: 64 ビット整数から 32 ビット整数にキャストしています
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);