Oracle Solaris Studio 12.2: C ユーザーガイド

7.3 LP64 データ型モデルへの変換

この節では実際の例を使用して、コードを変換したときに発生する可能性のある一般的な問題をいくつか紹介します。対応する lint の警告がある場合は、その警告も示します。

7.3.1 整数とポインタのサイズの変更

ILP32 コンパイル環境では整数とポインタは同じサイズであるため、コードには、この前提に立って作成されているものがあります。アドレス演算では、ポインタはしばしば int または unsigned int に型変換されます。LP64 コンパイル環境への変換では、ポインタは long に型変換してください。これは、ILP32 と LP64 データ型モデルで、long とポインタが同じサイズであるためです。明示的に 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);

7.3.2 整数とロング整数のサイズの変更

ILP32 データ型モデルでは実際には整数とロング整数が区別されないため、ほとんどの場合、既存のコードでは区別なしに整数とロング整数が使用されています。整数とロング整数が区別なしに使用されているコードは、修正して ILP32 と LP64 両方のデータ型モデルの条件に準拠するようにしてください。ILP32 データ型モデルでは整数とロング整数はともに 32 ビットですが、LP64 データ型モデルではロング整数は 64 ビットです。

次の例を考えてみましょう。


int waiting;
long w_io;
long w_swap;
...
waiting = w_io + w_swap;

%
warning: assignment of 64-bit integer to 32-bit integer

LP64 データ型モデルでは、int や unsigned int 型配列に比べて、long や unsigned long 型などのの大きな配列が原因で、重大なパフォーマンスの低下を招くことがあります。long や unsigned long 型の大きな配列はまた、キャッシュミスの大幅な増加や使用メモリーの増加の原因になることもあります。

このため、アプリケーションの用途上 long 型と同程度に int 型で問題がないならば、long 型ではなく int 型を使用する方が安全です。

ポインタ型配列の代わりに int 型配列を使用すべきという意見もあります。一部 C 言語で開発されたアプリケーションは、LP64 データ型モデルに変換すると、深刻なパフォーマンスの低下を招きます。これは、そうした C 言語で開発されたアプリケーションが多数の大きなポインタ型配列に依存しているためです。

7.3.3 符号拡張

型の変換と拡張規則はいくぶん曖昧ですから、64 ビットコンパイル環境への移行で、符号拡張はよく問題になります。符号拡張の問題を避けるには、明示的な型変換を使用して、意図した結果を得られるようにしてください。

符号拡張が発生する理由を理解するには、ISO C の変換規則の知識が役立ちます。32 ビットと 64 ビットコンパイル環境間で最大の符号拡張問題を引き起こすと思われる変換規則は、次の処理で適用されます。

次のコードを 64 ビットプログラムとしてコンパイルすると、addra.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);
}

ここで符号拡張が起きるのは、次のように変換規則が適用されるためです。


% cc -o test64 -xarch=v9 test.c
% ./test64
addr 0xffffffff80000000
addr 0x80000000
%

同じ例を 32 ビットプログラムとしてコンパイルすると、符号拡張はまったく表示されません。


cc -o test test.c
%test

addr 0x80000000
addr 0x80000000

変換規則の詳細については、ANSI/ISO C 規格の仕様書を参照してください。この規格には通常の演算変換や整数定数に関する有用な規則も規定されています。

7.3.4 整数の代わりのポインタ演算

ポインタ演算が常にデータ型モデルから独立しているのに対し、整数は独立していないことがあるため、一般的には整数を使用するより、ポインタ演算を使用する方がよいでしょう。また、通常、ポインタ演算を使用することによって、コードを簡単にすることもできます。次の例を考えてみましょう。


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;

7.3.5 構造体

アプリケーションの内部データ構造体に穴がないか検査してください。境界整列条件を満たすには、構造体のフィールドとフィールドの間にパディングをします。このパディングは、ロング整数またはポインタフィールドが LP64 データ型モデル用に 64 ビットになったときに適用します。SPARC プラットフォームの 64 ビットコンパイル環境では、あらゆる種類の構造体が、その中の最大量のサイズに合わせて整列されます。構造体を整列し直すときは、ロング整数およびポインタフィールドを構造体の先頭に移動するという簡単な規則に従ってください。次の例を考えてみましょう。


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 */

7.3.6 共用体

ILP32 と LP64 データ型モデルの間では、共用体のフィールドのサイズが変わる可能性があるため、共用体は必ず検査してください。


typedef union {
   double _d;
   long _l[2];
} llx_t;

修正版は次のようになります。


typedef union {
   double _d;
   int _l[2];
} llx_t;

7.3.7 型定数

精度が足りないと、一部の定数式でデータが失われることがあります。定数式でデータ型を指定するときは明示的に行なってください。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;

7.3.8 暗黙の宣言に対する注意

-xc99=none を使用する場合、C コンパイラは、モジュールで使用されていて、外部定義または宣言されてない関数や変数をすべて整数とみなします。このようにして使用されるロング整数やポインタは、コンパイラの暗黙の整数宣言によって切り捨てられます。この問題を避けるには、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);
}

7.3.9 sizeof( ) は符号なし long

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

7.3.10 型変換で意図を明確にする

変換規則により、関係式は扱いにくいことがあります。必要に応じて型変換を追加することによって、式の評価方法を明示するようにしてください。

7.3.11 書式文字列の変換操作を検査する

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

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