ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris Studio 12.3: C ユーザーガイド Oracle Solaris Studio 12.3 Information Library (日本語) |
この節では実際の例を使用して、コードを変換したときに発生する可能性のある一般的な問題をいくつか紹介します。対応する lint の警告がある場合は、その警告も示します。
ILP32 コンパイル環境では整数とポインタは同じサイズであるため、一部のコードはこの前提に依存しています。アドレス演算では、ポインタはしばしば int または unsigned int に型変換されます。LP64 コンパイル環境への変換では、ポインタは long に型変換してください。これは、ILP32 と LP64 データ型モデルで、long とポインタが同じサイズであるためです。明示的に unsigned long を使用するのではなく、代わりに uintptr_t を使用してください。意図がより正確に表現され、コードの移植性が向上し、将来の変更に容易に対応できます。次の例を考えてみましょう。
char *p; p = (char *) ((int)p & PAGEOFFSET); % warning: conversion of pointer loses bits
変更後のバージョン:
char *p; p = (char *) ((uintptr_t)p & PAGEOFFSET);
ILP32 データ型モデルでは実際には整数とロング整数が区別されないため、ほとんどの場合、既存のコードでは区別なしに整数とロング整数が使用されています。整数とロング整数が区別なしに使用されているコードは、修正して ILP32 と LP64 両方のデータ型モデルの条件に準拠するようにしてください。ILP32 データ型モデルでは integer と long はともに 32 ビットですが、LP64 データ型モデルでは long は 64 ビットです。
次の例を考えてみましょう。
int waiting; long w_io; long w_swap; ... waiting = w_io + w_swap; % warning: assignment of 64-bit integer to 32-bit integer
さらに、long または unsigned long の大きな配列は、int または unsigned int の配列と比較して、LP64 データ型モデルでパフォーマンスを顕著に低下させる可能性があります。long または unsigned long の大きな配列は、キャッシュミスの大幅な増加や消費メモリーの増加の原因になることもあります。
このため、アプリケーション目的のために int が long と同程度に機能する場合は、long ではなく int を使用してください。
この説明は、ポインタ配列の代わりに int の配列を使用する場合にも当てはまります。一部の C アプリケーションは、LP64 データ型モデルへの変換後に、深刻なパフォーマンスの低下を招きます。これは、それらが多数の大きなポインタ型配列に依存しているためです。
型の変換と拡張規則はいくぶん曖昧ですから、64 ビットコンパイル環境への移行で、符号拡張はよく問題になります。符号拡張の問題を避けるには、明示的な型変換を使用して、意図した結果を得られるようにしてください。
符号拡張が発生する理由を理解するために、ISO 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; /* 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 は、整数拡張規則により符号なし int から int に変換されます。つまり、式の a.base << 13 は int 型ですが、符号拡張はまだ発生していません。
式の a.base << 13 は int 型ですが、符号付きと符号なし整数拡張規則により、addr に代入する前に long、次に符号なし 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
変換規則の詳細については、ANSI/ISO C 規格の仕様書を参照してください。この規格には通常の演算変換や整数定数に関する有用な規則も規定されています。
ポインタ演算が常にデータ型モデルから独立しているのに対し、整数は独立していないことがあるため、通常は整数を使用するより、ポインタ演算を使用する方がよいでしょう。また、通常、ポインタ演算を使用することによって、コードを簡単にすることもできます。次の例を考えてみましょう。
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 ビットになったときに適用されます。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 */
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;
-xc99=none を使用する場合、C コンパイラは、モジュールで使用されていて、外部で定義または宣言されてない関数や変数をすべて整数とみなします。このようにして使用される long およびポインタ型のデータはすべて、コンパイラの暗黙の整数宣言によって切り捨てられます。C モジュールではなく、ヘッダーに関数または変数のための適切な extern 宣言を置いてください。そして、その関数または変数を使用する 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); }
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
変換規則により、関係式は扱いにくいことがあります。必要に応じて型変換を追加することによって、式の評価方法を明示するようにしてください。
printf(3S)、sprintf(3S)、scanf(3S)、sscanf(3S) に対する書式文字列が long またはポインタ型引数を受け付けられるようになっていることを確認してください。pointer 引数については、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);
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);