Solaris 64 ビット 開発ガイド

第 4 章 アプリケーションの変換

32 ビットから 64 ビットに変換アプリケーションを変換する際には、次の 2 つの基本的な問題があります。

通常は、できるだけ少ない数の #ifdef を使って 1 つのソースだけを管理する方が、複数のソースツリーを管理するよりも便利です。この章では、32 ビット環境と 64 ビット環境の両方で正しく動作するコードを書くためのガイドラインを示します。既存のコードを変換するのに必要なことは、再コンパイルして、64 ビットライブラリと再リンクするだけです。コードの変更が必要になった場合のために、変換を簡単に実行するためのツールについてもこの章で説明します。

データ型モデル

すでに説明したように、32 ビットと 64 ビットの環境の大きな違いは、データ型モデルです。

32 ビットアプリケーションに使用される C データ型モデルは ILP32 で、intlong、およびポインタが 32 ビットであるためそのように呼ばれています。LP64 データ型モデルは、64 ビットアプリケーション用の C データ型モデルで、業界の企業コンソーシアムで合意を得ています。この LP64 データ型モデルは、long とポインタが 64 ビットに拡大されたためそのように呼ばれています。ほかの C データ型 intshortchar は、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 

char

変更なし 

short

16 

変更なし 

int

32 

変更なし 

long

32 

64

long long

64 

変更なし 

ポインタ

32 

64

enum

32 

変更なし 

float

32 

変更なし 

double

64 

変更なし 

long double

128 

変更なし 

intlong、およびポインタ型を区別なく使用する旧式の 32 ビットアプリケーションもあります。long とポインタのサイズが LP64 データ型モデルで大きくなっています。この点から、32 ビットから 64 ビットへの多くの変換の問題が発生する可能性があるということを認識する必要があります。

さらに意図するプログラム処理を示すためには、宣言とキャストが重要になります。たとえば、データ型が変わると式の評価方法が影響を受ける可能性があります。データ型のサイズが変更された場合には、C の標準の変換規則は影響を受けます。意図する内容を明確に示すには、定数の型を宣言する必要があります。キャストを式に入れることによって、式を確実に意図するように評価させることも必要です。これは特に、符号拡張の場合に当てはまります。この場合、目的の処理を正しく示すには、明示的にキャストする必要があります。

その他の問題としては、組み込みの C 演算子、書式文字列、アセンブリ言語、互換性、および相互運用性の問題があります。

この章の以降の節で、次のようにこれらの問題の対処方法を紹介します。

単一ソースコードの実装

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> ファイル

<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> の主な機能は、次のとおりです。

これらについては以降の節で説明します。

固定幅整数型

<inttypes.h> で提供される固定幅整数型には、int8_tint16_tint32_tint64_tuint8_tuint16_tuint32_tuint64_t などの符号付き整数型および符号なし整数型があります。特定のビット数を格納できる最小の整数型として定義される派生型には、int_least8_tint_least64_tuint_least8_tuint_least64_t があります。

これらの固定幅型を無制限に使用しないでください。たとえば、int はこれまでと同様に、ループカウンタやファイル記述子などについて使用でき、long は配列のインデックスに使用できます。固定幅型は、次に示すような明示的なバイナリ表現に使用してください。

uintptr_t とその他の有用なデータ型

<inttypes.h> によって提供されるその他の型として、ポインタを格納するために十分なサイズの符号付き整数型および符号なし整数型があります。これらの型には、intptr_tuintptr_t があります。さらに、intmax_t および uintmax_t という (ビット単位で) 最長の符号付きおよび符号なしデータ型があります。

uintptr_t 型をポインタ用の整数型として使用する方が、unsigned long のような基本データ型を使用するよりも便利です。unsigned long は、IPL32 と LP64 データ型モデルの両方でポインタと同じサイズですが、uintptr_t を使用すると、uintptr_t の定義を変更するだけで異なるデータ型モデルを使用できます。このため、他の多くのシステムに移植が可能となります。またこれによって、C プログラムコード中に意図する処理をより明確に記述することができます。

intptr_tuintptr_t 型は、アドレス計算をする際にポインタをキャストするのに非常に役に立ちます。long または unsigned long の代わりにこれらを使用することができます。


注 –

通常は、uintptr_t を使用してキャストする方が、intptr_t を使用するよりも安全です。特に比較の場合はこの方法が安全です。


定数マクロ

マクロは、定数のサイズと符号を指定するために使用できます。マクロには、INT8_C(c)、...、INT64_C(c)UINT8_C(c)、...、UINT64_C(c) があります。基本的にこれらのマクロは、必要な場合に定数の後ろに lulll、または ull を置きます。たとえば、INT64_C(1) は、定数 1 の後ろに ILP32 の場合は ll を、LP64 の場合は l を付加します。

定数を最大のデータ型にするためのマクロには、INTMAX_C(c)UINTMAX_C(c) があります。これらのマクロは、「LP64 への変換のためのガイドライン」で説明している定数の型を指定するのに非常に役に立ちます。

<inttypes.h> によって定義される制限値

<inttypes.h> に定義されている制限値は、さまざまな整数型の最小値および最大値を指定する定数です。このファイルには、INT8_MIN、...、 INT64_MININT8_MAX、...、INT64_MAX、およびこれらの符号なし定数の、各固定幅型の最小値と最大値が指定されています。

最小サイズ型のそれぞれの最小値と最大値も指定されています。すなわち、INT_LEAST8_MIN、...、 INT_LEAST64_MININT_LEAST8_MAX、...、INT_LEAST64_MAX、およびこれらの符号なし定数です。

サポートされている整数型のうちの最大の型の最小値と最大値も定義されています。これらには、INTMAX_MININTMAX_MAX、およびそれらの符号なしのものがあります。

書式文字列マクロ

printfscanf の書式指示子を指定するためのマクロも <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 ユーザーズガイド』を参照してください。

32 ビットと 64 ビット環境の lint

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) のマニュアルページを参照してください。

LP64 への変換のためのガイドライン

lint(1) を使用する際には、すべての問題が lint(1) によって警告として検出されるわけではないこと、変更が不要な点についても lint(1) によって警告として出力されることがある、ということを憶えておいてください。警告の内容は、目的と照らし合わせて調べてください。これから示す例では、コードを変換する際に遭遇する可能性が高い問題を説明します。適切な場所で、lint(1) に相当する警告が現れます。

int とポインタが同じサイズであると仮定しない

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);

intlong が同じサイズであると仮定しない

intlong は、ILP32 では実際には区別されないため、意図的あるいは非意図的にそれらは交換可能であると仮定して、既存のコードの多くで区別することなく使用されています。このように仮定しているコードは、ILP32 および LP64 で動作するように変更する必要があります。ILP32 データ型モデルでは intlong の両方が 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 ビットの整数値間において、符号拡張の問題の原因になることがある変換規則は、次のとおりです。

  1. 整数の昇格

    charshort、列挙型、またはビットフィールド型は、符号付き / 符号なしに関わらず、int を必要とする式の中に使用できます。int が元の型の取り得る値をすべて格納できる場合、その値は int に変換されます。そうでない場合は、unsigned int に変換されます。

  2. 符号付きおよび符号なし整数間の変換

    負の符号付き整数が、サイズが同じまたはより大きい型の符号なし整数に昇格される場合、最初に大きい型の符号付きの値に昇格され、その後符号なしの値に変換されます。

変換規則についての詳細は、ANSI C 規格を参照してください。この規格には、通常の算術変換や整数定数についての規則が規定されています。

64 ビットプログラムとしてコンパイルした場合、次の例の addr 変数は、addr および a.base が符号なしの型であっても符号付きの型に拡張されます。


例 4–1 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);
}

このように符号拡張が発生するのは、変換規則が次のように適用されるからです。

  1. a.base が、整数の昇格規則によって、unsigned int から int に変換されます。このため、式 a.base << 13int 型ですが、符号拡張はまだ発生していません。

  2. 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;

定数の型指定

一部の定数式では、精度が不足するためにデータが失われる可能性があります。このような問題を検出するのは非常に困難です。定数式にデータ型を明示的に指定してください。各整数定数の後に (uUlL) を組合せたものを追加します。キャストを使用して定数式のデータ型を指定することもできます。次に例を示します。

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);
}

sizeofunsigned long である

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 を使用する意味があるかどうかチェックする

long は ILP32 データ型モデルでは 32 ビット、LP64 データ型モデルでは 64 ビットなので、以前は long として定義されたものが不適切または不要になることがあります。このような場合は、より移植性の高い派生型を使うこともできます。

上述の理由で、LP64 データ型モデルにおいて多くの派生型が変更されている場合があります。たとえば、pid_t は 32 ビット 環境では long のままですが、64 ビット 環境では int です。LP64 コンパイル環境用に変更された派生型のリストについては、付録 A 「派生型の変更」を参照してください。

明示的な 32 ビット 対 64 ビット プロトタイプのために #ifdef を使う

32 ビットおよび 64 ビット用にそれぞれ固有のインタフェースが必要な場合があります。ヘッダーで _LP64 または _ILP32 という機能テストマクロを使用することによって、それぞれのインタフェースを設けることができます。同様に、32 ビットおよび 64 ビット環境で動作させるコードに、それぞれのコンパイル環境に応じて適切な #ifdef を使用する必要がある場合もあります。

アルゴリズムの変更

コードを 64 ビット安全にした後、コードを再検討して、アルゴリズムおよびデータ構造が意図どおりであることを確認してください。データ型が大きいほど、データ構造体はより大きい空間を使用します。コードのパフォーマンスも同様に変化する場合があります。これらのことを考えて、コードを適切に修正する必要があるかもしれません。

チェックリスト (64 ビットに変換する前に)

以下の各項目を確認していくことによって、コードを 64 ビットに変換する必要があるかどうかを判断することができます。

プログラミング例

次に示すプログラミング例 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 ビットのアプリケーションを作成します。