JavaScript is required to for searching.
ナビゲーションリンクをスキップ
印刷ビューの終了
Oracle Solaris Studio 12.3: C ユーザーガイド     Oracle Solaris Studio 12.3 Information Library (日本語)
search filter icon
search icon

ドキュメントの情報

はじめに

1.  C コンパイラの紹介

2.  C コンパイラ実装に固有の情報

3.  C コードの並列化

4.  lint ソースコード検査プログラム

5.  型に基づく別名解析

6.  ISO C への移行

7.  64 ビット環境に対応するアプリケーションへの変換

7.1 データ型モデルの相違点

7.2 単一ソースコードの実現

7.2.1 派生型

7.2.1.1 <sys/types.h>

7.2.1.2 <inttypes.h>

固定幅の整数型

unintptr_t などの便利な型

定数マクロ

制限

書式文字列マクロ

7.2.2 lint によるチェック

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

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

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

7.3.3 符号拡張

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

7.3.5 構造体

7.3.6 共用体

7.3.7 型定数

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

7.3.9 sizeof( ) は符号なし long

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

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

7.4 変換に関するその他の注意事項

7.4.1 注: サイズが大きくなった派生型

7.4.2 変更の副作用の検査

7.4.3 long のリテラル使用の効果持続の確認

7.4.4 明示的な 32 ビットと 64 ビットプロトタイプに対する #ifdef の使用

7.4.5 呼び出し規則の変更

7.4.6 アルゴリズムの変更

7.5 変換前の確認事項

8.  cscope: 対話的な C プログラムの検査

A.  機能別コンパイラオプション

B.  C コンパイラオプションリファレンス

C.  ISO/IEC C 99 の処理系定義の動作

D.  C99 の機能

E.  ISO/IEC C90 の処理系定義の動作

F.  ISO C データ表現

G.  パフォーマンスチューニング

H.  Oracle Solaris Studio C: K&R C と ISO C の違い

索引

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

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

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

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

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

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 データ型モデルへの変換後に、深刻なパフォーマンスの低下を招きます。これは、それらが多数の大きなポインタ型配列に依存しているためです。

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 -m64 test.c
% ./test64
addr 0xffffffff80000000
addr 0x80000000
%

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

cc -o test -m32 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 構造体

アプリケーションの内部データ構造体に穴がないか検査してください。境界整列条件を満たすには、構造体のフィールドとフィールドの間にパディングをします。この臨時パディングは、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 */

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 コンパイラは、モジュールで使用されていて、外部で定義または宣言されてない関数や変数をすべて整数とみなします。このようにして使用される 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);
}

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 引数については、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);