32 ビットから 64 ビットに変換アプリケーションを変換する際には、次の 2 つの基本的な問題があります。
データ型の整合性および異なるデータ型モデル
異なるデータ型モデルを使ったアプリケーション間の相互運用
通常は、できるだけ少ない数の #ifdef を使って 1 つのソースだけを管理する方が、複数のソースツリーを管理するよりも便利です。この章では、32 ビット環境と 64 ビット環境の両方で正しく動作するコードを書くためのガイドラインを示します。既存のコードを変換するのに必要なことは、再コンパイルして、64 ビットライブラリと再リンクするだけです。コードの変更が必要になった場合のために、変換を簡単に実行するためのツールについてもこの章で説明します。
すでに説明したように、32 ビットと 64 ビットの環境の大きな違いは、データ型モデルです。
32 ビットアプリケーションに使用される C データ型モデルは ILP32 で、int
、long
、およびポインタが 32 ビットであるためそのように呼ばれています。LP64 データ型モデルは、64 ビットアプリケーション用の C データ型モデルで、業界の企業コンソーシアムで合意を得ています。この LP64 データ型モデルは、long
とポインタが 64 ビットに拡大されたためそのように呼ばれています。ほかの C データ型 int
、short
、char
は、ILP32 データ型モデルと同じです。
次に示すプログラミング例 foo.c では、LP64 と ILP32 のデータ型モデルの効果を直接的に示しています。同じプログラムを 32 ビットまたは 64 ビットプログラムとしてコンパイルすることができます。
#include <stdio.h> int main(int argc, char *argv[]) { (void) printf("char is \t\t%lu bytes\n", sizeof (char)); (void) printf("short is \t%lu bytes\n", sizeof (short)); (void) printf("int is \t\t%lu bytes\n", sizeof (int)); (void) printf("long is \t\t%lu bytes\n", sizeof (long)); (void) printf("long long is \t\t%lu bytes\n", sizeof (long long)); (void) printf("pointer is \t%lu bytes\n", sizeof (void *)); return (0); }
32 ビットコンパイルの結果は、次のようになります。
% cc -O -o foo32 foo.c % foo32 char is 1 bytes short is 2 bytes int is 4 bytes long is 4 bytes long long is 8 bytes pointer is 4 bytes |
64 ビットコンパイルの結果は、次のようになります。
% cc -xarch=generic64 -O -o foo64 foo.c % foo64 char is 1 bytes short is 2 bytes int is 4 bytes long is 8 bytes long long is 8 bytes pointer is 8 bytes |
デフォルトのコンパイル環境は、移植性を最大限にするように設計されているため、32 ビットのアプリケーションを作成します。
次に示すように C の各整数データ型間の標準的な関係は変わりません。
sizeof (char) <= sizeof (short) <= sizeof (int) <= sizeof (long)
表 4–1 に C の基本データ型、およびそれらの LP32 と LP64 のデータ型モデルのビットサイズを示します。
表 4–1 データ型サイズ (単位 : ビット)
C データ型 |
ILP32 |
LP64 |
---|---|---|
|
8 |
変更なし |
|
16 |
変更なし |
|
32 |
変更なし |
|
32 |
64 |
|
64 |
変更なし |
|
32 |
64 |
|
32 |
変更なし |
|
32 |
変更なし |
|
64 |
変更なし |
|
128 |
変更なし |
int
、long
、およびポインタ型を区別なく使用する旧式の 32 ビットアプリケーションもあります。long
とポインタのサイズが LP64 データ型モデルで大きくなっています。この点から、32 ビットから 64 ビットへの多くの変換の問題が発生する可能性があるということを認識する必要があります。
さらに意図するプログラム処理を示すためには、宣言とキャストが重要になります。たとえば、データ型が変わると式の評価方法が影響を受ける可能性があります。データ型のサイズが変更された場合には、C の標準の変換規則は影響を受けます。意図する内容を明確に示すには、定数の型を宣言する必要があります。キャストを式に入れることによって、式を確実に意図するように評価させることも必要です。これは特に、符号拡張の場合に当てはまります。この場合、目的の処理を正しく示すには、明示的にキャストする必要があります。
その他の問題としては、組み込みの C 演算子、書式文字列、アセンブリ言語、互換性、および相互運用性の問題があります。
この章の以降の節で、次のようにこれらの問題の対処方法を紹介します。
これまで概要を示した問題の詳細な説明
コードを 32 ビットおよび 64 ビットに対して安全にするのに有用な、派生型とインクルードファイルの解説
コードを 64 ビット安全にするためのツールの紹介
32 ビットおよび 64 ビット環境間でコードを移植可能にするための一般的規則
32 ビットおよび 64 ビットコンパイルをサポートする単一ソースコードを書く際に役立つ、アプリケーション開発者向けの資源について説明します。
システムインクルードファイル <sys/types.h> と <inttypes.h> には、アプリケーションを 32 ビット および 64 ビット安全にするために使用できる定数、マクロ、および派生型が含まれています。これらの詳細についてはこのマニュアルでは説明していませんが、その一部はこの章の以降の節および付録 A 「派生型の変更」で説明しています。
<sys/types.h> をインクルードするアプリケーションのソースファイルでは、<sys/isa_defs.h> をインクルードすることによってプログラミングモデルシンボル _LP64 と _ILP32 の定義を利用できるようになります。
プリプロセッサシンボル (_LP64 と _ILP32) およびマクロ (_LITTLE_ENDIAN と _BIG_ENDIAN) については、types (3HEAD) を参照してください。
システム派生型は、コードを 32 ビットおよび 64 ビット安全にするのに便利です。これは、派生型自身が ILP32 および LP64 のデータ型モデルに対して安全であるからです。一般に、変更を可能にするために派生型を使用しておくと便利です。後でデータ型モデルが変更された場合に、または異なるプラットフォームに移植する場合に、アプリケーションそのものではなく、システム派生型を変更するだけで済みます。
<sys/types.h> ヘッダーには、必要に応じて使用される多数の基本的な派生型が含まれています。特に次のものは重要です。
clock_t
システムの時間をクロック刻み (clock tick) で表します。
dev_t
デバイス番号に使用される型です。
off_t
ファイルサイズとオフセット用に使用される型です。
ptrdiff_t
2 つのポインタの減算結果を示す符号付き整数型です。
size_t
メモリー内のオブジェクトのサイズ (バイト単位) 用に使用される型です。
ssize_t
バイト数またはエラーのどちらを返すこともある関数によって使用される「符号付きサイズ」型です。
time_t
秒単位の時間用に使用される型です。
これらの型はすべて、ILP32 コンパイル環境では 32 ビット、LP64 コンパイル環境では 64 ビットになります。
これらの型の一部の使用方法については、「LP64 への変換のためのガイドライン」で詳しく説明しています。
<inttypes.h>
ファイル定数、マクロ、および派生型を定義するために、インクルードファイル <inttypes.h> が Solaris 2.6 リリースに追加されました。これにより、コンパイル環境とは無関係に、プログラマが記述したコードをサイズ指定されたデータ項目と明示的に互換性を持たせることができます。このファイルには、8 ビット、16 ビット、32 ビット、および 64 ビットのオブジェクトを操作するための機構が含まれてます。このインクルードファイルは、ANSI C の原案の一部で、ISO/JTC1/SC22/WG14 C 委員会による現在の ISO C 標準、つまり ISO/IEC 9899:1990 プログラミング言語 - C の改訂案を反映しています。
<inttypes.h> の主な機能は、次のとおりです。
固定幅整数型の集合
uintptr_t
とその他の有用なデータ型
定数マクロ
制限値
書式文字列マクロ
これらについては以降の節で説明します。
<inttypes.h> で提供される固定幅整数型には、int8_t
、int16_t
、int32_t
、int64_t
、uint8_t
、uint16_t
、uint32_t
、uint64_t
などの符号付き整数型および符号なし整数型があります。特定のビット数を格納できる最小の整数型として定義される派生型には、int_least8_t
、int_least64_t
、uint_least8_t
、uint_least64_t
があります。
これらの固定幅型を無制限に使用しないでください。たとえば、int
はこれまでと同様に、ループカウンタやファイル記述子などについて使用でき、long
は配列のインデックスに使用できます。固定幅型は、次に示すような明示的なバイナリ表現に使用してください。
ディスク上のデータ
送受信データ
ハードウェアレジスタ
バイナリインタフェース仕様 (明示的にサイズの決められたオブジェクトがあるもの、または 32 ビットプログラムと 64 ビットプログラム間での共有や通信を含むもの)
バイナリデータ構造 (32 ビットプログラムおよび 64 ビットプログラムが、共有メモリー、共有ファイルなどを介して使用するもの)
uintptr_t
とその他の有用なデータ型
<inttypes.h> によって提供されるその他の型として、ポインタを格納するために十分なサイズの符号付き整数型および符号なし整数型があります。これらの型には、intptr_t
と uintptr_t
があります。さらに、intmax_t
および uintmax_t
という (ビット単位で) 最長の符号付きおよび符号なしデータ型があります。
uintptr_t
型をポインタ用の整数型として使用する方が、unsigned long
のような基本データ型を使用するよりも便利です。unsigned long
は、IPL32 と LP64 データ型モデルの両方でポインタと同じサイズですが、uintptr_t
を使用すると、uintptr_t
の定義を変更するだけで異なるデータ型モデルを使用できます。このため、他の多くのシステムに移植が可能となります。またこれによって、C プログラムコード中に意図する処理をより明確に記述することができます。
intptr_t
と uintptr_t
型は、アドレス計算をする際にポインタをキャストするのに非常に役に立ちます。long
または unsigned long
の代わりにこれらを使用することができます。
通常は、uintptr_t
を使用してキャストする方が、intptr_t
を使用するよりも安全です。特に比較の場合はこの方法が安全です。
マクロは、定数のサイズと符号を指定するために使用できます。マクロには、INT8_C(c)、...、INT64_C(c)、UINT8_C(c)、...、UINT64_C(c) があります。基本的にこれらのマクロは、必要な場合に定数の後ろに l、ul、ll、または ull を置きます。たとえば、INT64_C(1) は、定数 1 の後ろに ILP32 の場合は ll を、LP64 の場合は l を付加します。
定数を最大のデータ型にするためのマクロには、INTMAX_C(c) と UINTMAX_C(c) があります。これらのマクロは、「LP64 への変換のためのガイドライン」で説明している定数の型を指定するのに非常に役に立ちます。
<inttypes.h> に定義されている制限値は、さまざまな整数型の最小値および最大値を指定する定数です。このファイルには、INT8_MIN
、...、 INT64_MIN
、INT8_MAX
、...、INT64_MAX
、およびこれらの符号なし定数の、各固定幅型の最小値と最大値が指定されています。
最小サイズ型のそれぞれの最小値と最大値も指定されています。すなわち、INT_LEAST8_MIN
、...、 INT_LEAST64_MIN
、INT_LEAST8_MAX
、...、INT_LEAST64_MAX
、およびこれらの符号なし定数です。
サポートされている整数型のうちの最大の型の最小値と最大値も定義されています。これらには、INTMAX_MIN と INTMAX_MAX、およびそれらの符号なしのものがあります。
printf と scanf の書式指示子を指定するためのマクロも <inttypes.h> にあります。これらのマクロは、引数のビット数がマクロ名に組み込まれている場合に、初期指示子の先頭に l または ll を付加することによって引数を long
または long long
として指定します。
printf(3C) 書式指示子用のマクロは、10 進、8 進、符号なし、16 進の、8 ビット、16 ビット、32 ビット、64 ビットの整数、最小整数型と最大整数型を出力するためのものです。64 ビットの整数を 16 進表記で出力する例を、次に示します。
int64_t i; printf("i =%" PRIx64 "\n", i);
同様に、scanf(3C) 書式指示子用のマクロが、10 進、8 進、符号なし、および 16 進の 8 ビット、16 ビット、32 ビット、64 ビットの整数、ならびに最小整数型と最大整数型の読み込み用に提供されています。符号なし 64 ビットの 10 進整数を読み込む例を、次に示します。
uint64_t u; scanf("%" SCNu64 "\n", &u);
これらのマクロは、無制限に使用しないでください。これらは固定幅型と一緒に使用するのが最適な使用方法です。詳細は、「固定幅整数型」を参照してください。
Sun Studio 10 コンパイラで使用できる lint プログラムは、発生する可能性のある 64 ビット関連の問題を検出することができるので、コードを 64 ビット安全にするのに便利です。また、C コンパイラの -v コンパイルオプションも便利です。このオプションを使用すると、コンパイル時に通常のチェックに加えて、より厳しい意味解析上のチェックを行うことができます。さらに、引数として指定したファイルに対して lint に似たチェックも実行します。
C コンパイラの機能と lint については、『Sun Studio 10: C ユーザーズガイド』を参照してください。
lint は、32 ビットコードおよび 64 ビットコードの両方に使用することができます。32 ビット環境および 64 ビット環境の両方で実行するコードには、-errchk=longptr64 オプションを使用します。-errchk=longptr64 オプションは、ロング整数とポインタのサイズが 64 ビットで、かつ普通の整数が 32 ビットである環境への移植性を調べるのに使用します。
-Xarch=v9 オプションは、64 ビット SPARC 環境で実行する lint コードに対して使用します。64 ビット SPARC 上で実行するコードに対して、発生する可能性がある 64 ビット関連の問題について警告を表示するようにするには、-Xarch=v9 オプションと共に -errchk=longptr64 オプションを使用します。
Solaris 10 リリースから、-Xarch=amd64 オプションは 64 ビット AMD 環境で実行する lint コードに対して使用します。
lint には -D__sparcv9 オプションを使用しないでください。
lint オプションの説明は、『Sun Studio 10: C ユーザーズガイド』を参照してください。
警告がある場合、lint(1) は、エラーが発生した行の行番号、問題を説明する警告メッセージ、およびポインタが関わっているかどうかを出力します。関連する型のサイズも示されます。ポインタが関わっているかどうかおよび型のサイズを知ることは、64 ビット関連の問題を特定し、さらに 32 ビットとそれより小さい型との間の既存の問題を避けるのに役に立ちます。
lint は発生する可能性がある 64 ビット関連の問題に関して警告を出すことはできますが、問題をすべて検出できるわけではありません。また lint が出力する警告の中には 64 ビット関連以外の問題が含まれていることもあります。警告が出されていても、そのコードは特定の意図に沿って記述されていてアプリケーションにとって適切なコードである、という場合がよくあります。
次のサンプルプログラムと lint(1) 出力は、64 ビットクリーンコード以外のコードで発生する lint 警告のよくある例を示したものです。
1 #include <inttypes.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 static char chararray[] = "abcdefghijklmnopqrstuvwxyz"; 6 7 static char *myfunc(int i) 8 { 9 return(& chararray[i]); 10 } 11 12 void main(void) 13 { 14 int intx; 15 long longx; 16 char *ptrx; 17 18 (void) scanf("%d", &longx); 19 intx = longx; 20 ptrx = myfunc(longx); 21 (void) printf("%d\n", longx); 22 intx = ptrx; 23 ptrx = intx; 24 intx = (int)longx; 25 ptrx = (char *)intx; 26 intx = 2147483648L; 27 intx = (int) 2147483648L; 28 ptrx = myfunc(2147483648L); 29 } (19) warning: assignment of 64-bit integer to 32-bit integer (20) warning: passing 64-bit integer arg, expecting 32-bit integer: myfunc(arg 1) (22) warning: improper pointer/integer combination: op "=" (22) warning: conversion of pointer loses bits (23) warning: improper pointer/integer combination: op "=" (23) warning: cast to pointer from 32-bit integer (24) warning: cast from 64-bit integer to 32-bit integer (25) warning: cast to pointer from 32-bit integer (26) warning: 64-bit constant truncated to 32 bits by assignment (27) warning: cast from 64-bit integer constant expression to 32-bit integer (28) warning: passing 64-bit integer constant arg, expecting 32-bit integer: myfunc(arg 1) function argument ( number ) type inconsistent with format scanf (arg 2) long * :: (format) int * t.c(18) printf (arg 2) long :: (format) int t.c(21)
(このコードサンプルの 27 行目の lint 警告は、定数式がキャストされる型に当てはまらないときにのみ出力されます。)
/*LINTED*/ コメントをその前の行に置くと、任意のソース行に対する警告を抑止できます。これは、意図的に特別な動作をコード中に記述したい場合には役に立ちます。例としては、キャストや代入の場合があります。/*LINTED*/ コメントは、実際に問題がある場合にもそれを検出しないようにするので、使用する際は十分に注意してください。詳細は、『Sun Studio 10: C ユーザーズガイド』または lint(1) のマニュアルページを参照してください。
lint(1) を使用する際には、すべての問題が lint(1) によって警告として検出されるわけではないこと、変更が不要な点についても lint(1) によって警告として出力されることがある、ということを憶えておいてください。警告の内容は、目的と照らし合わせて調べてください。これから示す例では、コードを変換する際に遭遇する可能性が高い問題を説明します。適切な場所で、lint(1) に相当する警告が現れます。
int
とポインタは、ILP32 環境では同じサイズであるため、多くのコードがこの仮定に基づいています。ポインタは、アドレス計算の際に int
または unsigned int
にキャストされることがあります。また、ポインタは long
にキャストすることもできます。long
とポインタは、ILP32 および LP64 で同じサイズだからです。unsigned long
を明示的に使うかわりに、uintptr_t
を使用してください。uintptr_t
は、意図することがより明確にわかり、コードをより移植可能なものにして、その結果、将来変更があっても影響されないようにするためです。次に例を示します。
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 で動作するように変更する必要があります。ILP32 データ型モデルでは int
と 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
意図しない符号の拡張は、64 ビットに変換する際によく発生する問題です。符号の拡張について lint(1) は警告を出さないため、実際に問題が発生する前に問題を検出するのは困難です。さらに、型変換および型昇格に関する規則には不明瞭な部分もあります。意図しない符号拡張の問題を解決するには、意図する結果を得ることができるように明示的なキャストを使用する必要があります。
ANSI C の変換規則を理解しておくと、なぜ符号の拡張が発生するかを理解するのに役立ちます。32 ビットおよび 64 ビットの整数値間において、符号拡張の問題の原因になることがある変換規則は、次のとおりです。
整数の昇格
char
、short
、列挙型、またはビットフィールド型は、符号付き / 符号なしに関わらず、int
を必要とする式の中に使用できます。int
が元の型の取り得る値をすべて格納できる場合、その値は int
に変換されます。そうでない場合は、unsigned int
に変換されます。
符号付きおよび符号なし整数間の変換
負の符号付き整数が、サイズが同じまたはより大きい型の符号なし整数に昇格される場合、最初に大きい型の符号付きの値に昇格され、その後符号なしの値に変換されます。
変換規則についての詳細は、ANSI C 規格を参照してください。この規格には、通常の算術変換や整数定数についての規則が規定されています。
64 ビットプログラムとしてコンパイルした場合、次の例の addr
変数は、addr
および a.base
が符号なしの型であっても符号付きの型に拡張されます。
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 が、整数の昇格規則によって、unsigned int
から int
に変換されます。このため、式 a.base << 13 は int
型ですが、符号拡張はまだ発生していません。
式 a.base << 13 は、int
型ですが、符号付きおよび符号なしの整数昇格規則によって最初に long
へ変換され、その後 unsigned long
へ変換された後、addr
に代入されます。符号拡張は、この式が 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;
LP64 データ型モデルでは long およびポインタフィールドが 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; /* 右辺が int 型のため j には 0 が代入される */
このコードは、次のように使用してください。
int i = 32; long j = 1L << i;
特定のコンパイルモードのコンパイラは、モジュールに使用されかつ extern
として定義または宣言されていない関数または変数に対してそのデータ型を int
とみなします。long
およびポインタがこのように使用された場合、コンパイラの暗黙的な int
宣言によって切り捨てられます。関数または変数に対する適切な 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 <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
として実装される size_t
の実効的なデータ型を持ちます。sizeof は、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
関係式には変換規則があるので、注意を要します。必要に応じてキャストを追加して、式をどのように評価するかを明示的に記述してください。
printf(3C)、sprintf(3C)、 scanf(3C)、 sscanf(3C) が long
またはポインタ引数に対して使用される場合、それらを long
またはポインタ引数用に変更する必要がある場合があります。ポインタ引数を 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);
アプリケーションを完全に 64 ビットプログラムに変換する際によく発生する、その他の問題を取り上げます。
64 ビットアプリケーション環境で 64 ビットを表すために、多くの派生型が変更されました。この変更は、 32 ビットアプリケーションには影響を与えませんが、これらの型で記述されるデータを使用またはエクスポートする 64 ビットアプリケーションは、再評価して正しく動作することを確認する必要があります。たとえば、utmpx(4) ファイルを直接操作するアプリケーションについては再確認が必要です。64 ビットアプリケーション環境で正しく動作するように、これらのファイルへ直接アクセスしないようにしてください。代わりに getutxent(3C) および関連する関数を使用してください。
付録 A 「派生型の変更」には、変更された派生型の一覧が記載されています。
ある部分で型の変更を行なった結果、別の部分で予期しない 64 ビット 変換が起きることがあります。たとえば、以前には int
を戻していたが現在は ssize_t
を返す関数に対しては、すべての呼び出し側をチェックする必要があります。
long
は ILP32 データ型モデルでは 32 ビット、LP64 データ型モデルでは 64 ビットなので、以前は long
として定義されたものが不適切または不要になることがあります。このような場合は、より移植性の高い派生型を使うこともできます。
上述の理由で、LP64 データ型モデルにおいて多くの派生型が変更されている場合があります。たとえば、pid_t
は 32 ビット 環境では long
のままですが、64 ビット 環境では int
です。LP64 コンパイル環境用に変更された派生型のリストについては、付録 A 「派生型の変更」を参照してください。
32 ビットおよび 64 ビット用にそれぞれ固有のインタフェースが必要な場合があります。ヘッダーで _LP64 または _ILP32 という機能テストマクロを使用することによって、それぞれのインタフェースを設けることができます。同様に、32 ビットおよび 64 ビット環境で動作させるコードに、それぞれのコンパイル環境に応じて適切な #ifdef を使用する必要がある場合もあります。
コードを 64 ビット安全にした後、コードを再検討して、アルゴリズムおよびデータ構造が意図どおりであることを確認してください。データ型が大きいほど、データ構造体はより大きい空間を使用します。コードのパフォーマンスも同様に変化する場合があります。これらのことを考えて、コードを適切に修正する必要があるかもしれません。
以下の各項目を確認していくことによって、コードを 64 ビットに変換する必要があるかどうかを判断することができます。
このマニュアル全体をお読みください。特に 「LP64 への変換のためのガイドライン」に重点を置いて読んでください。
すべてのデータ構造体とインタフェースを再検討して、それらが 64 ビット環境でも有効であることを確認してください。
<sys/types.h> をコードにインクルードして、_ILP32 または _LP64 の定義やその他の基本派生型を組み込んでください。
関数プロトタイプおよび非局所的な有効範囲を持つ外部宣言をヘッダーに移動し、それらのヘッダーをコードにインクルードしてください。
-errchk=longptr64 を指定して lint(1) を実行し、各警告メッセージを確認してください (すべての警告どおりに変更が必要なわけではありません)。結果として必要になる変更によっては、32 ビットおよび 64 ビットモードで lint(1) を再実行する必要があります。
アプリケーションを 64 ビット専用としてだけで提供する予定でない場合は、コードを 32 ビットおよび 64 ビットとしてコンパイルしてください。
32 ビットオペレーティングシステム上で 32 ビットバージョンのアプリケーションを実行し、さらに、64 ビットオペレーティングシステム上で 64 ビットバージョンを実行して、アプリケーションをテストしてください。64 ビットオペレーティングシステム上で 32 ビットバージョンのテストも実行できますが、その必要はありません。
次に示すプログラミング例 foo.c では、LP64 と ILP32 のデータ型モデルの効果を直接的に示しています。同じプログラムを 32 ビットまたは 64 ビットプログラムとしてコンパイルすることができます。
#include <stdio.h> int main(int argc, char *argv[]) { (void) printf("char is \t\t%lu bytes\n", sizeof (char)); (void) printf("short is \t%lu bytes\n", sizeof (short)); (void) printf("int is \t\t%lu bytes\n", sizeof (int)); (void) printf("long is \t\t%lu bytes\n", sizeof (long)); (void) printf("long long is \t\t%lu bytes\n", sizeof (long long)); (void) printf("pointer is \t%lu bytes\n", sizeof (void *)); return (0); }
32 ビットコンパイルの結果は、次のようになります。
% cc -O -o foo32 foo.c % foo32 char is 1 bytes short is 2 bytes int is 4 bytes long is 4 bytes long long is 8 bytes pointer is 4 bytes |
64 ビットコンパイルの結果は、次のようになります。
% cc -xarch=generic64 -O -o foo64 foo.c % foo64 char is 1 bytes short is 2 bytes int is 4 bytes long is 8 bytes long long is 8 bytes pointer is 8 bytes |
デフォルトのコンパイル環境は、移植性を最大限にするように設計されているため、32 ビットのアプリケーションを作成します。