C ユーザーズガイド |
第 8 章
アプリケーションの変換
この章では、32 ビットまたは 64 ビットのコンパイル環境用のコードを作成するために必要なことについて説明します。説明項目は次のとおりです。
32 ビット、64 ビット両方のコンパイル環境で動作するコードを作成または変更する場合、次の 2 つの基本的な問題に直面します。
通常、複数のソースツリーを保守するより、
#ifdef
をできるだけ少なくした 1 つのソースコードを保守する方が便利です。このため、この付録では、32 ビットと 64 ビット両方のコンパイラ環境で正しく機能するコードを作成する際のガイドラインを示します。場合によっては、現在のコードを再コンパイルして、64 ビットライブラリに再リンクすればよいだけのこともあります。しかし、コードの修正が必要になる場合もあり得るため、この付録では、こうした変換をより簡単に行うためのツールと参考情報について説明します。
- 「データ型モデルの相違点」 - 32 ビットと 64 ビット環境に関する用語を紹介し、基本的な相違点を簡単に説明します。
- 「単一ソースコードの実現」 - 32 ビットと 64 ビットの両方でコンパイルできる単一ソースコードの作成に使用できる資源をいくつか紹介します。
- 「LP64 データ型モデルへの変換」 - コードの変換で発生する可能性のある一般的な問題をいくつか紹介し、そうした問題に対応する
lint
警告がある場合は、その警告を示します。- 「その他の注意事項」 - コードの修正後に問題の解決を行うときの一般的なヒントを示します。
- 「変換前の確認事項」 - 変換を正しく行う上で役立ちます。
データ型モデルの相違点
32 ビットと 64 ビットコンパイル環境の最大の違いは、データ型モデルにあります。
32 ビットアプリケーション用の C のデータ型モデルは ILP32 モデルです。この名前は、integer、long、pointer が 32 ビットデータ型であることから名付けられています。long と pointer が 64 ビットの大きさになったことから名付けられた LP64 データ型モデルは、業界の関連企業から構成されるコンソーシアムが作成したものです。残りの C のデータ型の int、long long、short、char はどちらのデータ型モデルでも同じです。
C の整数型間の標準の関係は、次に示すようにデータ型モデルに関係なく有効です。
sizeof
(char)
<=
sizeof
(short)
<=
sizeof
(int)
<=
sizeof
(long)
ILP32 と LP64 データ型モデルの基本的な C のデータ型と対応するサイズ (単位: ビット) は、次の表に示すとおりです。
表 8-1 ILP32 と LP64 のデータ型のサイズ char 8 8 short 16 16 int 32 32 long 32 64 long long 64 64 pointer 32 64 enum 32 32 float 32 32 double 64 64 long double 128 128
現在の 32 ビットアプリケーションでは integer、pointer、long が同じサイズであるとみなされることが多くあります。LP64 データ型モデルでは、long と pointer のサイズが変更されているため、この変更だけでも、ILP32 から LP64 への変換で多くの問題が発生する可能性があります。
また、宣言と型変換を調べることも非常に重要です。データ型が変わると、式の評価方法が影響を受ける可能性があります。標準的な C の変換規則の働きも、データ型のサイズの変更の影響を受けます。意図したこと正しく示すには、定数の型を明示的に宣言してください。式で型変換を使用して、意図したとおりに式が評価されるようにすることもできます。このことは、意図したことを指示する上で明示的な型変換が欠かせない符号拡張部で特に必要になります。
単一ソースコードの実現
この節では、32 ビットと 64 ビットの両方でコンパイル可能な単一ソースコードの作成に使用できる資源をいくつか紹介します。
派生型
32 ビットと 64 ビットのどちらのコンパイル環境でも安全なコードにするには、システム派生型を使用します。一般的に、変更の可能性がある場合には派生型を使用することをお勧めします。派生データ型を使用すると、データ型モデルの変更あるいは移植に際して、システム派生型を変更すればよいだけになります。
システムインクルードファイルの
<sys/type.h>
および<inttypes.h>
には、32 ビットと 64 ビットのどちらにも安全なアプリケーションの作成に役立つ定数、マクロ、派生型が含まれています。
<sys/types.h>
アプリケーションのソースファイルに
<sys/types.h>
をインクルードして、_LP64
および_ILP32
の定義を使用できるようにしてください。このヘッダーには、必要に応じて使用される基本派生型もいくつか含まれています。特に次は大切です。
clock_t
- クロックの刻み数でシステム時間を表します。dev_t
- デバイス番号に使用されます。off_t
- ファイルのサイズとオフセットに使用されます。ptrdiff_t
- 2 つのポインタの減算結果用の符号付き整数型です。size_t
- メモリー上のオブジェクトのサイズをバイト数で表します。ssize_t
- バイト数あるいはエラー発生通知を返す関数によって使用されます。time_t
- 秒数で時間をカウントします。これらの派生型はすべて、ILP32 コンパイル環境では 32 ビット量のままですが、LP64 コンパイル環境では、64 ビット量になります。
<inttypes.h>
<inttypes.h>
インクルードファイルには、コンパイル環境に関係なく、明示的にサイズ指定されたデータ項目との互換性を持たせるのに役立つ定数、マクロ、派生型が含まれています。このファイルには、8、16、32、64 ビットオブジェクトを操作するための仕組みも含まれています。<inttypes.h>
は ANSI/ISO C 提案の一部であり、ISO C 規格 (ISO/IEC 9899:1990 プログラミング言語 C) の改訂に備えて ISO、JTC1、SC22、WG14 C 委員会が策定中の草案の一部を残しています。<inttypes.h>
に含まれることが議論されている基本機能としては、次があります。次に
<inttypes.h>
のこれらの基本機能について詳しく説明します。固定幅の整数型
<inttypes.h>
が提供する固定幅の整数型には、int8_t
、int_16t
、int32_t
、int64_t
などの符号付き整数型と、uint8_t
、uint_16t
、uint32_t
、uint64_t
などの符号なし整数型があります。指定数のビットを保持できる最小サイズの整数型として定義されている派生型としては、
int_least8_t、int_least16_t、int_least32_t
、int_least64_t
、uint_least8_t
、uint_least16_t
、uint_least32_t
、uint_least64_t
などがあります。ループカウンタやファイル記述子などの演算に整数を使用することは問題ありません。配列インデックスにロング整数を使用することも問題ありません。しかし、これらの固定幅型はむやみに使用しないでください。固定幅の型は、次の明示的なバイナリ表現に使用してください。
uninptr_t などの便利な型
<inttypes.h>
ファイルには、ポインタを保持するのに十分な大きさの符号付き整数型と符号なし整数型intptr_t
とuintptr_t
が含まれます。また、符号付きと符号なし整数型の中で最長 (ビット) の整数型であるintmax_t
とuintmax_t
も提供します。
unitptr_t
型は、unsigned long
などの基本型ではなく、ポインタ用の整数型として使用してください。ILP32 と LP64 コンパイル環境でunsigned long
とpointer
が同じサイズであるとしても、uintptr_t
を使用するということは、データ型モデルが変わった場合に、その影響を受けるのはuintptr_t
の定義だけになることを意味します。このため、他の多くのシステムにコードを移植できるようになります。また、これは、C で自分の意図していることをより明確に表現する手段になります。
intptr_t
およびuintptr_t
型は、アドレス演算でポインタの型変換を行うときに大変役立ちます。この目的には、long
やunsigned long
ではなく、intptr_t
とuintptr_t
型を使用してください。定数マクロ
定数のサイズと符号の指定には、
INT8_C(c)
〜INT64_C(c)
、UINT8_C(c)
〜UINT64_C(c)
マクロを使用してください。基本的に、これらのマクロは、必要に応じて定数の末尾に l、ul、ll、ull という文字列を付加します。たとえば、INT64_C(l) は、ILP32 では、定数 1 に ll、LP 64 では l を付加します。定数を最大型にするときは、
INTMAX_C(c)
とUINTMAX_C(c)
を使用してください。これらのマクロは、「LP64 データ型モデルへの変換」で説明している定数型を指定する際に大変役立ちます。制限値
<inttypes.h>
で定義されている上下制限は、いろいろな整数型の最小値と最大値を指示する定数です。これには、INT8_MIN
〜INT64_MIN
、INT8_MAX
〜INT64_MAX
などの固定幅型をそれぞれの符合なし型に対する最小値と最大値が含まれます。
<inttypes.h>
ファイルには、最小サイズのそれぞれの型に対する最小値と最大値も含まれます。これには、INT_LEAST8_MIN
〜INT_LEAST64_MIN
、INT_LEAST8_MAX
〜INT_LEAST64_MAX
型やこれらに対応する符号なし型があります。
また、<inttypes.h>
には、サポートされる最大整数型の最小値と最大値も定義されています。これには、INTMAX_MIN
、INTMAX_MAX
、これらに対応する符号なし型があります。書式文字列マクロ
<inttypes.h>
ファイルには、printf(3S)
およびscanf(3S)
の書式指示子を指定するマクロも含まれています。基本的にこれらのマクロは、引数のビット数がマクロ名に組み込まれていることを条件に、書式指示子の前に l または ll を付加して、引数がlong
またはlong lon
g のどちらであるかを示します。次の例に示すように、最小および最大整数型を 10 進、8 進、符号なし、16 進の形式で表示する、
printf(3S)
用のマクロがあります。
int64_t i;printf("i =%" PRIx64 "\n", i);同様に、最小および最大整数型を 10 進、8 進、符号なし、16 進の形式で読み取る、
scanf(3S)
用のマクロがあります。
uint64_t u;scanf("%" SCNu64 "\n", &u);これらのマクロはむやみに使用しないでください。「固定幅の整数型」で説明したように、固定幅型に対して使用することが最も適しています。
ツール
Sun WorkShop には、64 ビット環境でエラーになりそうな問題を検出する機能拡張版の
lint
プログラムが付属しています。また、C コンパイラに-v
オプションを使用すると、より厳密な意味検査も行われます。-v
オプションは、指定されたファイルに対してlint
に似た検査もいくつか行います。64 ビット環境で安全なコードにするには、Solaris 7 オペレーティングシステムに含まれているヘッダーファイルを使用してください。このヘッダーファイルには、64 ビットコンパイル環境用の派生型とデータ構造体の正しい定義が含まれています。
lint
32 ビットおよび 64 ビットの両方のコンパイル環境用に作成したコードの検査には、lint を使用してください。LP64 の警告を生成するには、
-errchk=longptr64
オプションを使用します。また、ロング整数とポインタのサイズが 64 ビットで普通の整数のサイズが 32 ビットの環境への移植性を検査する場合も-errchk=longptr64
フラグを使用してください。-errchk=longptr64
フラグは、明示的な型変換が使用されているときにも、ポインタ式とロング整数式の普通の整数への代入を検査します。64 ビットコンパイル環境でだけ実行するコードを検査する場合は、
lint
の-Xarch=v9
オプションを使用してください。警告する場合、
lint
は問題のコードの行番号とその問題の内容や、ポインタが関連するかどうかを示すメッセージを表示します。また、関係するデータ型のサイズも示します。ポインタが関係していること、データ型のサイズがわかれば、64 ビットの問題を特定し、32 ビットとそれより小さい型の間に以前から存在している問題を避けることできます。ただし、64 ビット環境でエラーになる可能性のある問題について警告を出すといっても、
lint
によってすべての問題が検出できるわけではありません。多くの場合、意図したとおりであり、正しいコードであっても、警告は出されます。行の前に
/*LINTED*/
の形式のコメントを挿入すると、特定の行に対する警告を抑止できます。この機能は、リンクや型変換や代入などの行を無視させる場合に役立ちます。ただし、現実には存在する問題が隠される可能性があるため、/*LINTED*/
コメントを使用するときは、細心の注意を払ってください。詳細は、lint
(1) のマニュアルページを参照してください。LP64 データ型モデルへの変換
この節では実際の例を使用して、コードを変換したときに発生する可能性のある一般的な問題をいくつか紹介します。対応する
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/ISO C の変換規則の知識が役立ちます。32 ビットと 64 ビットコンパイル環境間で最大の符号拡張問題を引き起こすと思われる変換規則は、次の処理で適用されます。
- 整数の拡張
整数を必要とする式では、符号の有無に関係なく、
char
、short
、enumerated type
、ビットフィールドを使用することができます。整数が元の型が取り得る値をすべて保持できる場合、値は整数に変換され、それ以外の場合は、符号なし整数に変換されます。
- 符号付きと符号なし整数間の変換
負符号付きの整数を同じまたは大きい型の符号なし整数に拡張する場合は、最初に大きな型符号付き整数に拡張され、次に符号なし値に変換されます。
次のコードを 64 ビットプログラムとしてコンパイルすると、
addr
とa.base
の両方が符号なしの型であっても、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% ./test64addr 0xffffffff80000000addr 0x80000000%同じ例を 32 ビットプログラムとしてコンパイルすると、符号拡張はまったく表示されません。
cc -o test test.c%testaddr 0x80000000addr 0x80000000変換規則の詳細については、ANSI/ISO 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
またはポインタフィールドが 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; /* RHS が整数式のため j は 0 になる */
int i = 32;long j = 1L << i;暗黙の宣言に対する注意
C コンパイラは、モジュールで使用されていて、外部定義または宣言されてない関数や変数をすべて整数とみなします。このようにして使用されるロング整数やポインタは、コンパイラの暗黙の整数宣言によって切り捨てられます。この問題を避けるには、C モジュールではなく、ヘッダーに関数または変数に対する適切な extern 宣言を挿入してください。そして、その関数または変数を使用する C モジュールにヘッダーをインクルードしてください。システムヘッダーによって定義されている関数あるいは変数であっても、コードに正しいヘッダーをインクルードする必要があります。次の例を考えてみましょう。
#include <unistd.h>#include <stdio.h>intmain(int argc, char *argv[]){char *name = getlogin();(void) printf("login = %s\n", name);return (0);}
sizeof()
は unsigned longLP64 データ型モデルでは、
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:%lu%%%lu from heap got%lx.%lx returns%lx\n",nbytes, align, raddr, raddr + alloc, addr);その他の注意事項
この節では、アプリケーションを完全な 64 ビットプログラムに変換するときに発生する問題を取り上げます。
サイズが大きくなった派生型
いくつかの派生型が変更されており、64 ビットコンパイル環境で 64 ビット量を表すようになっています。32 ビットアプリケーションがこの変更の影響を受けることはありませんが、これらの型で表されるデータを消費またはエクスポートする 64 ビットアプリケーションは、評価し直す必要があります。たとえば
utmp
(4) あるいはutmpx
(4) ファイルを直接操作するアプリケーションがこれにあたります。64 ビットアプリケーション環境で正しく動作させるには、utmp
またはutmpx
ファイルに直接にアクセスしないようにしてください。代わりに、getutxent
(3C) および関連する系列の関数を使用します。変更の副作用の検査
ある場所で型を変更したために、別のコード部分で予想外の 64 ビット変換が発生することがあります。たとえば、それまで int を返していて、現在は
ssize_t
を返すようになった関数のすべての呼び出し元を検査してください。long のリテラル使用の合理性の確認
long と定義された変数は、ILP32 データ型モデルでは 32 ビット、LP64 データ型モデルでは 64 ビットです。可能な場合は、こうした変数を定義し直し、移植性に優れた派生型を使用することによって問題の発生を回避してください。
これに関連して、LP64 データ型モデルでは、いくつかの派生型が変更されています。たとえば、
pid_t
は 32 ビット環境ではlong
のままですが、64 ビット環境ではint
になります。明示的な 32 ビットと 64 ビットプロトタイプに対する
#ifdef
の使用場合によっては、32 ビットや 64 ビット専用のインタフェースを使用しなければならないことがあります。そうしたインタフェースには、ヘッダー中で _LP64 または _ILP32 の機能テストマクロを指定して区別できます。同様に、32 ビットまた 64 ビット環境で動作するコードでは、コンパイルモードに従って適切な
#ifdef
を使用する必要があります。呼び出し規則の変更
構造体を値によって渡し、SPARC V9 用にコードをコンパイルした場合、その構造体は、コピーへのポインタとしてではなく、レジスタ中で渡されます (構造体がそうできるほどの大きさの場合)。その場合、C コードと手書きのアセンブリコード間で構造体を渡そうとすると、問題が起きることがあります。
浮動小数点パラメータも同様に機能します。値で渡される浮動小数点値は浮動小数点レジスタ中で渡されます。
アルゴリズムの変更
64 ビット環境で安全なコードを作成したら、コードを見直して、アルゴリズムとデータ構造体が正しく機能することを確認してください。データ構造体のデータ型が大きいほど、使用する空間が増えることがあります。コードのパフォーマンスも影響を受けるかもしれません。こうしたことに注意し、必要に応じてコードを修正してください。
変換前の確認事項
コードを 64 ビットに変換するにあたっては次の事項を確認してください。
- すべてのデータ構造体とインタフェースを見直して、64 ビット環境でも問題がないことを確認します。
- コードに
<sys/types.h>
(または少なくとも<sys/isa_defs.h>
) をインクルードして、多数の基本派生型とともに _ILP32 または _LP64 の定義を取り込みます。- スコープが局所ではない関数プロトタイプと外部宣言はヘッダーに移動し、コード中にヘッダーをインクルードします。
-errchk=longptr64
と-D_sparcv9
フラグを使用してlint
を実行し、すべての警告に目を通してください。必ずしもすべての警告について、コードの変更が必要になるわけではありません。変更によっては、32 ビットと 64 ビットモードの両方でlint
を再度実行してください。- アプリケーションの 64 ビット版だけ提供するのでない限り、32 ビットと 64 ビットの両方でコードをコンパイルしてください。
- アプリケーションのテストは、32 ビット版は 32 ビットオペレーティングシステム上で、64 ビット版は 64 ビットオペレーティングシステム上で行なってください。32 ビット版は、64 ビットオペレーティングシステム上でテストすることもできます。
サン・マイクロシステムズ株式会社 Copyright information. All rights reserved. |
ホーム | 目次 | 前ページへ | 次ページへ | 索引 |