Sun ANSI C コンパイラは、プログラミング言語用米国標準規格 C、ANSI/ISO 9899-1990 に記述されている C 言語と互換性があります。この章では、Sun ANSI C コンパイラに固有の部分を説明します。
cc は通常 /tmp ディレクトリに一時ファイルを作成します。環境変数 TMPDIR を設定すると、別のディレクトリを指定することができます。TMPDIR が有効なディレクトリ名でない場合は、/tmp が使用されます。-xtemp オプションと環境変数 TMPDIR では、-xtemp が優先されます。
$ TMPDIR=<ディレクトリ>; export TMPDIR
% setenv TMPDIR <ディレクトリ>
.sbinit(5)ファイルが格納されるディレクトリの絶対パス名。この変数は、-xsb または -xsbfast フラグが使用されている場合にのみ使用されます。
(SPARC) 詳細については、「環境変数 」を参照してください。
符号なし保存算術変換に依存しているプログラムの動作は以前とは異なっています。これは、K&R C と ANSI C の最も大きな違いです。
K&R による『プログラミング言語 C』(共立出版、1978 年) の中では、unsigned は型を正確に 1 つだけ指定していました。そのときは unsigned char、unsigned short、unsigned long はありませんでしたが、その後間もなくほとんどの C コンパイラがこれらを追加しました。
以前の C コンパイラでは、符号なし保存規則が拡張 (promotion) のときに使用されます。すなわち、符号なし型が拡大を必要とするときは符号なし型に拡大され、符号なし型が符号付き型と混合されると結果として符号なし型になります。
これに対して ANSI C の規則は、値保存と呼ばれています。この保存規則では、結果の型はオペランドの型のサイズ間の関係によって決定されます。unsigned char または unsigned short が拡張されると、結果の型は int の大きさがその値全部を表現するのに十分なものであれば int です。それ以外の場合、結果の型は unsigned int です。値保存規則は、ほとんどの式で最も妥当な算術結果となります。
コンパイラは、-Xt モードと -Xs モードでだけ符号なし保存拡張規則を使用します。他のモード、すなわち -Xc モードと -Xa モードでは、値保存拡張規則が使用されます。-xtransition オプションを使用した場合は、使用される拡張規則によって動作が異なる可能性のある式があれば、コンパイラは式ごとに警告を発します。
予約語 asm は、-Xc を除くすべてのコンパイラモードで予約されています。予約語 _asm は asm の同義語で、すべてのコンパイラモードの下で使用できますが、-Xc モードで使用した場合は警告が出されます。
asm("<文字列>"):
ここで、<文字列> は有効なアセンブリ言語文です。
main() { int i; /* i = 10 */ asm("mov 10,%l0"); asm("st %l0,[%fp-8]"); printf("i = %d¥n",i); } % cc foo.c % a.out i = 10 %
asm 文は関数の本体内部に指定しなければなりません。
コンパイラが効果的にループの並列実行を行うには、ある種の左辺値 (lvalue) が異なる記憶領域を指しているかどうかを判別する必要があります。別名とは、記憶領域が異なっていない左辺値のことです。オブジェクトを示す 2 つのポインタが別名かどうかを判別するのは、プログラム全体の解析を必要とすることがあるため、難しく時間のかかる作業です。
void vsq(int n, double * a, double * b) { int i; for (i=0; i<n; i++) b[i] = a[i] * a[i]; }
コンパイラは、ポインタ a と b が異なるオブジェクトにアクセスすることがわかっていれば 、ループの異なる繰り返しの実行を並列化することができます。ポインタ a と b でアクセスされるオブジェクトが重なりあっている場合には、コンパイラが並列でループを実行するのは安全ではありません。コンパイル時には、コンパイラは関数 vsq() を単純に解析しただけでは a と b でアクセスするオブジェクトが重なり合っているかどうかがわかりません。この情報を得るにはプログラム全体の解析が必要です。
コンパイラがポインタ別名解析を実行できるよう、制限付きポインタを使用して異なるオ ブジェクトを示すポインタを指定します。制限付きポインタをサポートするために、キーワード _Restrict が Sun ANSI C コンパイラによって拡張として認識されます。次に vsq() の関数引数を制限付きポインタとして宣言する例を示します。
void vsq(int n, double * _Restrict a, double * _Restrict b)
ポインタ a と b が制限付きポインタとして宣言されると、コンパイラは a と b が示す記憶域の領域が異なるものだと認識します。この別名情報により、コンパイラはループを並列化することができます。
_Restrict キーワードは volatile と同じような型修飾子で、ポインタ型だけを修飾します。_Restrict は、コンパイルモード -Xa (デフォルト) と -Xt でのみキーワードとして認識されます。これらの 2 つのモードでは、コンパイラはマクロ __RESTRICT を定義して、ユーザーが制限付きポインタを用いて移植可能なコードを書けるようにします。
コンパイラはマクロ __RESTRICT を定義して、ユーザーが制限付きポインタを用いて移植可能なコードを書けるようにします。たとえば、次のコードは Sun ANSI C コンパイラのすべてのコンパイルモードで使用でき、制限付きポインタをサポートしていない他のコンパイラでも使用できます。
#ifdef __RESTRICT #define restrict _Restrict #else #define restrict #endif void vsq(int n, double * restrict a, double * restrict b) { int i; for (i=0; i<n; i++) b[i] = a[i] * a[i]; }
制限付きポインタが ANSI C 標準の一部に加わると、"restrict" がキーワードになるものと思われます。ユーザーが vsq() でのように制限付きポインタを用いてコードを書く場合の一例を以下に示します。
#define restrict _Restrict
こうしておけば、ANSI C で restrict がキーワードになっても、変更を最小限にとどめることができます。Sun ANSI C コンパイラではキーワードとして _Restrict を使用しています。これが処理系の名前空間に入っていて、ユーザーの名前空間にある識別子と衝突しないためです。
ユーザーがソースコードを変更したくない場合があります。その場合、コマンド行オプション -xrestrict を用いて、ポインタ値のある関数引数を制限付きポインタとして扱うよう指定することができます。詳細は、「-xrestrict=f 」を参照してください。
関数リストを指定すると、指定した関数内のポインタ引数は制限付きポインタとして扱われます。関数リストを指定しない場合は、C ファイル全体のすべてのポインタ引数が制限付きとして扱われます。たとえば、-xrestrict=vsq は、関数 vsq() の例で示したポインタ a と b をキーワード _Restrict で修飾します。
_Restrict は正しく使用することが重要です。制限付きポインタとして修飾されているポインタが、同一オブジェクトを指していると、ループは誤って並列化されることがあり、未定義の動作が発生します。たとえば、関数 vsq() のポインタ a と b が重なり合ったオブジェクトを指し、b[i] と a[i+1] は同じオブジェクトであるとします。a と b が制限付きポインタとして宣言されなければ、ループは順次に実行されます。a と b が誤って制限付きポインタとして宣言されると、コンパイラはループの実行を並列化することがあります。b[i+1] の計算は、b[i] の計算後に実行される必要が生じるため、これは安全とは言えません。
Sun ANSI C コンパイラにはデータ型 long long および unsigned long long があり、これらはデータ型 long と類似しています。long には 32 ビットの情報を格納できるのに対し、long long には 64 ビットの情報を格納できます。long long は -Xc モードでは使用できません。
long long データ型を出力または入力するには、変換指定子の前に "ll" の接頭辞を付けてください。たとえば、long long データ型をもつ変数 llvar を符号付き 10 進形式で出力するには、次のように指定します。
printf("%lld¥n", llvar);
2 項演算子によっては、両方のオペランドの型を共通の型にするために変換することがあります。この時、結果の型も共通の型となります。この変換は通常の算術変換と呼ばれます。
どちらか一方のオペランドが long double 型である場合、もう一方のオペランドは long double に変換されます。
一方のオペランドが double 型を持つ場合、もう一方のオペランドは double に変換されます。
一方のオペランドが float 型を持つ場合、もう一方のオペランドは float に変換されます。
これ以外の場合は、汎整数拡張が両方のオペランドで実行されます。次に以下の規則が適用されます。
一方のオペランドが unsigned long long int 型を持つ場合、もう一方の演算子は unsigned long long int に変換されます。
一方のオペランドが long long int 型を持つ場合、もう一方の演算子は long long int に変換されます。
一方のオペランドが unsigned long int 型を持つ場合、もう一方の演算子は unsigned long int に変換されます。
一方のオペランドが long int 型を持ち、もう一方が unsigned int 型を持つ場合、両オペランドは unsigned long int に変換されます。
一方のオペランドが long int 型を持つ場合、もう一方のオペランドは long int に変換されます。
一方のオペランドが unsigned int 型を持つ場合、もう一方のオペランドは unsigned int に変換されます。
これ以外の場合、両オペランドは int 型になります。
ここでは、Sun ANSI C コンパイラに固有の定数に関する情報について説明します。
下表に示すように、10 進数、8 進数、16 進数の定数に接尾辞を付けて型を示すことができます。
表 3-1 データ型の接尾辞
接尾辞 |
型 |
---|---|
u または U |
unsigned |
l または L |
long |
ll または LL |
long long |
lu 、LU 、Lu 、lU 、ul 、uL 、Ul 、UL のいずれか |
unsigned long |
llu 、LLU 、LLu 、llU 、ull 、ULL 、uLL 、Ull のいずれか | unsigned long long |
コンパイラが接尾辞を持たない定数の型を割り当てる場合、定数の大きさに応じて、次のリストから値が表現できる最初の型を使用します。
int
long int
unsigned long int
long long int
unsigned long long int
エスケープシーケンスではない複数の文字からなる文字定数は、各文字が持つ数値から派生する値を持ちます。たとえば定数 '123' の持つ値は以下のようになります。
表 3-2 複数文字からなる定数 (ANSI)
0 |
'3' |
'2' |
'1' |
あるいは 0x333231 です。
-Xs オプション使用の場合、あるいは他の非 ANSI の C では、この値は以下のようになります。
表 3-3 複数文字からなる定数 (非 ANSI)
0 |
'1' |
'2' |
'3' |
あるいは 0x313233 です。
C コンパイルシステムに含まれる標準ヘッダーファイルをインクルードするには、次の書式を使用します。
#include <stdio.h>
山括弧(<>)を使用するとプリプロセッサはシステム内の標準の場所、通常は /usr/include ディレクトリにあるヘッダーファイルを検索します。
ユーザーが自分のディレクトリに格納したヘッダーファイルの場合は、次のように書式が異なります。
#include "header.h"
二重引用符 ("") を使用すると、プリプロセッサはまず、この #include 行を含むファイルが格納されているディレクトリの中で header.h を検索します。
ヘッダーファイルがインクルードされたソースファイルと同じディレクトリにない場合は、cc コマンドで -I オプションを使用して、ヘッダーファイルが格納されているディレクトリのパスを指定してください。たとえば次のように、ソースファイル mycode.c の中で stdio.h と header.h をインクルードしたとします。
#include <stdio.h> #include "header.h"
この header.h が ../defs ディレクトリに格納されている場合は、次のコマンドを実行します。
% cc -I../defs mycode.c
この場合、プリプロセッサが header.h を検索する順序は、最初が mycode.c を含むディレクトリ、次が ../defs ディレクトリ、最後が標準の場所となります。stdio.h については最初が ../defs、次が標準の場所となります。相違点は、現ディレクトリを検索するのは名前を二重引用符で囲んだヘッダーファイルを検索する場合だけです。
-I オプションは 1 つの cc コマンド行の中で複数回指定することができます。指定したディレクトリをプリプロセッサが検索する順序は、コマンド行での指定順序と同じです。 cc のコマンド行では複数のオプションを指定できます。
% cc -o prog -I../defs mycode.c
IEEE 754 のデフォルトの浮動小数点演算機能は「無停止」であり、アンダーフローは「段階的」です。ここではこの 2 つの用語を説明します。詳細については『数値計算ガイド』を参照してください。
「無停止」とは、ゼロによる除算、浮動小数点のオーバーフロー、不正演算例外などが生じても実行を停止しないことを意味します。たとえば次の式で、x はゼロ、y は正の数であるとします。
z = y / x;
デフォルトでは、z の値は +Inf になりますが、プログラムの実行は続けられます。ただし、-fnonstd オプションを使用した場合は、このコードによってプログラムが終了します (コアダンプなど)。
次に、段階的アンダーフローの動作を説明するために、次のようなコードを例として考えます。
x = 10; for (i = 0; i < LARGE_NUMBER; i++) x = x / 10;
ループを 1 回通ると x は 1 になり、2 回目で 0.1、3 回目で 0.01 と続き、やがてはマシンによって値を表現できる許容範囲の下限に到達します。
次にループを実行すると、表現可能な最小の数は次のようになると考えられます。
1.234567e-38
次にループを実行すると、仮数部から「盗んだ」ものを指数部に「与える」ことによって数値が修正され、次の式のようになります。
1.23456e-39
その次はさらに、
1.2345e-40
と続いていきます。これがデフォルト動作である「段階的アンダーフロー」です。非標準の動作では、仮数部から「盗む」ことをせず、通常は単に x をゼロに設定します。
ここでは、表明、プラグマ、および事前定義名について説明します。
#assert <述語> (<トークン列>)
<トークン列> は、表明の名前領域 (マクロ定義用の領域から分離されています) にある <述語> と関連付けられます。<述語> は識別子トークンでなければなりません。
#assert <述語>
これは <述語> が存在していることを表明しますが、それにトークン列を関連付けることはしません (-Xc モードを除く)。
コンパイラは、次のような事前定義された述語をデフォルトとして提供しています。
#assert system (unix) #assert machine (sparc) (SPARC) #assert machine (i386) (x86) #assert cpu (sparc) (SPARC) #assert cpu (i386) (x86)
lint は、次のような事前定義された述語をデフォルトとして提供しています (-Xc モードを除く)。
#assert lint (on)
表明は #unassert を使用して削除できます。この場合、#assert と同じ構文が使用されます。引数なしで #unassert を使用すると述語に対するすべての表明が削除され、表明を指定すればその表明だけが削除されます。
表明は、次の構文を持つ #if 文でテストすることができます。
#if #<述語>(<空でないトークン列>)
たとえば以下のように指定して、事前定義された述語 system をテストすることができます。
#if #system(unix)
これは真と評価されます。
以下の書式を持つ前処理行は、各処理系が定義した処理を指定します。
#pragma <プリプロセッサトークン>
次の #pragma はコンパイルシステムに認識されます。認識されなかったプラグマは無視されます。-v オプションを使用すると、認識されなかったプラグマについて警告が出されます。
整列プラグマで指定した <変数> のメモリーはデフォルト値によらず、すべて <整数> バイト境界に揃えられます。
<整数> には 2 の階乗 (1 〜 128) を指定します。有効な値は 1、2、4、8、16、32、64、128 です。
<変数> には大域または静的な変数を指定します。自動変数は指定できません。
指定された境界がデフォルトより小さい場合は、デフォルトが優先します。
プラグマ行は、その行に指定される変数の宣言よりも先になければなりません。先にないと無視されてしまいます。
プラグマ行で記述されているが、その後で宣言されていない変数は無視されます。たとえば次のようになります。
#pragma align 64 (aninteger, astring, astruct) int aninteger; static char astring[256]; struct astruct{int a; char *b;};
リストに指定したルーチンが直接にも間接にも大域データを読み取らないことを表明します。この表明により、そうしたルーチンへの呼び出し前後のコードをさらに最適化することができます。具体的には、代入文やストア命令をそうした呼び出しの前後に移動することができます。
このプラグマは、指定した関数のプロトタイプを宣言した後でのみ使用できます。大域アクセスに関する表明が真でない場合は、プログラムの動作は未定義になります。
指定した関数への呼び出しが復帰しないことをコンパイラのバックエンドに表明します。この表明により、オプティマイザは、指定された関数への呼び出しが戻らないと仮定して最適化を行うことができます。たとえば、レジスタの存続期間が呼び出し元で終了する場合は、さらに最適化率を高めることができます。
指定した関数が復帰した場合は、プログラムの動作は未定義になります。
次の例に示すように、このプラグマは、指定した関数のプロトタイプを宣言した後でのみ使用できます。
extern void exit(int); #pragma does_note_return(exit); extern void __assert(int); #pragma does_not_return(__assert);
リストに指定したルーチンが直接にも間接にも大域データを書き込まないことを表明します。この表明により、そうしたルーチンへの呼び出し前後のコードをさらに最適化することができます。具体的には、代入文やストア命令をそうした呼び出しの前後に移動することができます。
このプラグマは、指定した関数のプロトタイプを宣言した後でのみ使用できます。大域アクセスに関する表明が真でない場合は、プログラムの動作は未定義になります。
このエラーメッセージプラグマは、ソースプログラムの中から、C コンパイラおよび lint が発行するメッセージを制御可能にします。C コンパイラでは、警告メッセージに対してのみ有効です。C コンパイラの -w オプションを使用すると、このプラグマは無効になり、すべての警告メッセージが抑止されます。
#pragma error_messages (on, <タグ>... <タグ>)
on オプションは、先行する #pragma error_messages オプション (off オプションなど) をその時点で無効にして、-erroff オプションも無効にします。
#pragma error_messages (off, <タグ>... <タグ>)
off オプションは、C コンパイラまたは lint プログラムが指定トークンから始まる特定のメッセージを発行することを禁止します。この特定のエラーメッセージに対するプラグマの指定は、別の #pragma error_messages によって無効にされるか、コンパイルが終了するまで有効です。
#pragma error_messages (default, <タグ>... <タグ>)
default オプションは、指定タグについて、先行する #pragma error_messages 指令を無効にします。
main() ルーチンを呼び出した後、<関数 1> から <関数 n> までの関数 (終了関数) を呼び出します。この種の関数は、型が void で引数はあってはなりません。プログラム制御下でプログラムが終了したとき、またはこのプログラムを含む共有オブジェクトがメモリーから除去されたときに呼び出されます。次の「初期化関数」の場合と同様、終了関数もリンクエディタによって処理された順に実行されます。
実行可能プログラムの .comment セクション内に任意の <文字列> を格納します。
main() を呼び出す前に、<関数 1> から <関数 n> までの関数 (初期化関数) を呼び出します。この種の関数は、型が void で引数はあってはなりません。実行開始時にプログラムのメモリーイメージを構成しているときに呼び出されます。共有オブジェクトの中の初期値設定子は、その共有オブジェクトをメモリー内へ持っていく動作の間、つまりプログラムの開始中、または dlopen() のような動的ロード動作中に実行されます。初期化関数の呼び出しを順序付ける方法は、それがリンクエディタによって動的または静的に処理される順序に依存します。
指定したルーチン名のインライン化を制御します。このプラグマはファイル全体に対して有効です。このプラグマでは、大域的なインライン化のみ制御可能であり、呼び出し元固有の制御を行うことはできません。
このプラグマは、現在のファイル内にある、指定したルーチンに一致する呼び出しをインライン化するようコンパイラに提案します。この提案は、無視されることがあります。たとえば、関数本体が別のモジュールに存在していて、-xcrossfile オプションが使用されていない場合などです。
次の例に示すように、このプラグマは、指定した関数のプロトタイプを宣言した後でのみ使用できます。
static void foo(int); static int bar(int, char *); #pragma inline_routines(foo, bar);
-Xt モードまたは -Xs モードで unsigned の型を返す <関数> が、戻り値の型 int を持つように変更します。
詳細については 「直列プラグマ」を参照してください。
詳細については 「直列プラグマ」を参照してください。
詳細については 「並列プラグマ」を参照してください。
指定したルーチン名のインライン化を制御します。このプラグマはファイル全体に対して有効です。このプラグマでは、大域的なインライン化のみ制御可能であり、呼び出し元固有の制御を行うことはできません。
このプラグマは、現在のファイル内にある、指定したルーチンに一致する呼び出しをインライン化しないようコンパイラに提案します。
このプラグマは、指定した関数のプロトタイプを宣言した後でのみ使用できます。
ループのどの繰り返しでもメモリーの依存がないと指示します。つまり、ループのどの繰り返しの中でも、同じメモリーの参照は必要がないと指示します。このプラグマを指定すると、コンパイラ (パイプライナ) はループの 1 回の繰り返しの中で、より効率的に命令をスケジュールすることができます。ループの繰り返しの中でメモリーの依存があると、プログラムの実行結果は未定義になります。プラグマは現行ブロック内の次の for ループに適用されます。コンパイラはこの情報をレベル 3 以上の最適化に利用します。
<関数> は、現行の翻訳単位内の関数名を指定します。関数はプラグマより前に宣言されていなければなりません。またプラグマはその関数の定義より前に指定されていなければなりません。指定した関数 <関数> に対し、プラグマはその関数に一切の副作用がないことを宣言します。コンパイラはこの情報を、その関数を用いる最適化に利用することができます。関数に副作用があると、この関数を呼び出すプログラムの実行結果 は未定義になります。コンパイラはこの情報をレベル 3 以上の最適化に利用します。
指定した関数サブプログラムに対する最適化レベルを指定します。最適化レベルとしては 0〜5 を選択することができます。0 に設定すると、最適化は無効になります。このプラグマを使用する前に、関数サブプログラムのプロトタイプを作成しておく必要があります。
構造体のメンバーの境界整列を制御します。デフォルトでは、構造体のメンバーは、char 型なら 1 バイト、short 型なら 2 バイト、整数なら 4 バイトというように、その自然境界で整列させられます。n を指定する場合には、ゼロもしくはすべての構造体メンバーに対して最も厳密な自然境界整列となる 2 の乗数にします。
このプラグマを使用すると、構造体メンバーの境界整列をデフォルトとは異なるものにすることができます。たとえば、#pragma pack(2) を指定すると、int、long、long long、float、double、long double、pointer が、それぞれの自然境界ではなく、2 バイト境界に整列されます。
n がプラットフォームで最も厳密な整列を指示する値 (Intel では 4、SPARC v8 では 8、SPARC v9 では 16) か、それより大きな値の場合は、自然境界整列が有効になります。n が省略された場合も、メンバーは自然境界整列に戻ります。
#pragma pack(n) 指令は、その指令から次の #pragma pack 指令の間のすべての構造体の定義に適用されます。別の翻訳単位で同じ構造体に対して異なる #pragma pack の定義が行われている場合、プログラムは予期しない形でコンパイルに失敗することがあります。特に、#pragma pack(n) は、事前にコンパイルされたライブラリのインタフェースを定義するヘッダーをインクルードする前には使用しないでください。#pragma pack(n) は、プログラムコード内の境界整列を変更するすべての構造体の直前に挿入することをお勧めします。そして、その構造体の直後に #pragma pack() を続けてください。
このプラグマは、引数 n に正の整定数または 0 を受け入れます。このプラグマは、ループがパイプライン化可能で、ループによる依存の最小の依存距離が n であることを指定します。距離が 0 の場合、そのループは実質的には Fortran 形式の doall ループで、ターゲットプロセッサ上でパイプライン処理するべきであることを意味します。距離が 0 より大きい場合、コンパイラは n 回だけの連続繰り返しでパイプラインを試みます。プラグマは現行ブロック内の次の for ループに適用されます。コンパイラはこの情報をレベル 3 以上の最適化に利用します。
指定した関数があまり使用されないというヒントをコンパイラのバックエンドに与えます。このヒントにより、コンパイラは、プロファイル収集段階に負担をかけることなく、ルーチンの呼び出し元でプロファイルフィードバック方式の最適化を行うことができます。このプラグマは提案ですので、コンパイラのオプティマイザは、このプラグマに基づく最適化を行わないこともあります。
次の例に示すように、#pragma rarely_called プリプロセッサ指令は、指定した関数のプロトタイプを宣言した後でのみ使用できます。
extern void error (char *message); #pragma rarely_called(error);
このプラグマにより、オブジェクトコード中で外部定義された <旧外部参照名> の名前がすべて <新外部参照名> に置換されます。この結果、リンク時にリンカーは新しい名前だけを認識します。関数定義、初期設定子、または式のいずれかとして <旧外部参照名> を最初に使用した後、#pragma redefine_extname が指定されていると、結果は未定義になります (-Xs のモードではこのプラグマはサポートされていません)。
#pragma redefine_extname を使用できる場合、コンパイラは、事前に定義されたマクロの PRAGMA_REDEFINE_EXTNAME の定義を使用して、#pragma redefine_extname の有無に関係なく機能する移植可能なコードを作成できるようにします。
#pragma redefine_extname の目的は、関数名を変更できない場合に関数インタフェースを効率的に再定義する手段を提供することにあります。関数名を変更できない場合とは、たとえば、既存のプログラムとの互換性を保つために、ライブラリ内に、関数の古い定義と (新しいプログラムで使用する) 新しい定義の両方を維持する必要がある場合です。ライブラリに新しい名前で新しい関数定義を追加した場合に、このようなことが必要になります。新旧の名前と定義が存在する関数を宣言するヘッダーファイルで #pragma redefine_extname を使用すると、その関数が使用されるときは、必ずその関数の新しい定義でリンクされるようになります。
#if defined(__STDC__) #ifdef __PRAGMA_REDEFINE_EXTNAME extern int myroutine(const long *, int *); #pragma redefine_extname myroutine __fixed_myroutine #else /* __PRAGMA_REDEFINE_EXTNAME */ static int myroutine(const long * arg1, int * arg2) { extern int __myroutine(const long *, int*); return (__myroutine(arg1, arg2)); } #endif /* __PRAGMA_REDEFINE_EXTNAME */ #else /* __STDC__ */ #ifdef __PRAGMA_REDEFINE_EXTNAME extern int myroutine(); #pragma redefine_extnmae myroutine __fixed_myroutine #else /* __PRAGMA_REDEFINE_EXTNAME */ static int myroutine(arg1, arg2) long *arg1; int *arg2; { extern int __fixed_myroutine(); return (__fixed_myroutine(arg1, arg2)); } #endif /* __PRAGMA_REDEFINE_EXTNAME */ #endif /* __STDC__ */
指定した関数の戻り値が呼び出し元のどのメモリーとも別名処理されないことを表明します。つまり、この呼び出しでは、新しいメモリー位置が返されます。この表明により、オプティマイザはポインタ値を追跡して、メモリー位置を明確にし、ループのスケジューリングやパイプライン処理、並列化処理を改善することができます。表明が偽の場合には、プログラムの動作は未定義になります。
次の例に示すように、このプラグマは、指定した関数のプロトタイプを宣言した後でのみ使用できます。
void *malloc(unsigned); #pragma returns_new_memory(malloc);
通常の手続き呼び出しの制御フロー特性に反するルーチンのリスト <名前> [,<名前>]... を指定します。たとえば setjmp() の呼び出しの後ろに置かれた文は、他のルーチンに対する任意の呼び出しから到達できます。この文は longjmp() の呼び出しで到達されます。このようなルーチンは標準のフローグラフ解析を無効にしますので、それらを呼び出すルーチンは安全に最適化できません。したがって、それらはオプティマイザを抑止してコンパイルされます。
このプラグマは、引数 <展開係数> に正の整定数を受け入れます。プラグマは現行ブロック内の次の for ループに適用されます。展開係数が 1 以外の場合、指定されたループを指定の係数で展開するよう、コンパイラに指示します 。コンパイラは可能な限り、その展開係数を使用します。展開係数が 1 の場合、ループを展開してはならないことをコンパイラに指定します。コンパイラはこの情報をレベル 3 以上の最適化に利用します。
弱い大域シンボルを定義します。このプラグマは主にライブラリを構築するソースファイルの中で使用されます。リンカーは弱いシンボルを解決できなくてもエラーメッセージを表示しません。
#pragma weak <シンボル>
これは <シンボル> を弱いシンボルとして定義しています。<シンボル> の定義が見つからなくても、リンカーはメッセージ等を出さなくなります。
#pragma weak <シンボル 1> = <シンボル 2>
これは <シンボル 1> を、<シンボル 2> の別名の弱いシンボルと定義します。この形式のプラグマは、ソースファイルまたはそこにインクルードされたヘッダーファイルのいずれかで、<シンボル 2> を定義した同じ変換ユニットの中に限り使用できます。それ以外で使用された場合は、コンパイルエラーになります。
プログラムが <シンボル 1> を呼び出しますが、それがプログラム中で定義されていない場合には、<シンボル 1> がリンクライブラリ中の弱いシンボルになっていると、リンカーはライブラリにある定義を使用します。しかし、プログラム自身が <シンボル 1> を定義してある場合、プログラムでの定義が優先され、ライブラリに存在する <シンボル 1> の弱い大域定義は使用されません。プログラムが直接 <シンボル 2> を呼び出すと、ライブラリにある定義が使用されます。<シンボル 2> の定義が重複して行われるとエラーになります。
下の表に示す識別子は、オブジェクトに似たマクロとして事前に定義されています。
表 3-4 事前定義された識別子
識別子 |
説明 |
---|---|
_ _STDC_ _ |
_ _STDC_ _ 1 -Xc _ _STDC_ _ 0 -Xa, -Xt 未定義 -Xs |
コンパイラは -Xs モードで、_ _STDC_ _ が定義解除 (#undef _ _STDC_ _) されていると、警告を出します。
事前定義されているものは次のとおりです (-Xc モードでは無効)。
sun
unix
sparc (SPARC)
i386 (x86)
次の事前定義されているものは、あらゆるモードで有効です。
_ _sun
_ _unix
_ _SUNPRO_C=0x500
_ _'uname -s'_'uname -r' (例: _ _SunOS_5_7)
_ _sparc (SPARC)
_ _i386 (x86)
_ _BUILTIN_VA_ARG_INCR
_ _SVR4
_ _sparcv9 (-Xarch=v9、v9a )
コンパイラにより、次のオブジェクト形式のマクロが事前定義されます。
_ _PRAGMA_REDEFINE_EXTNAME
これにより、プラグマが認識されます。
_ _RESTRICT は、-Xa および -Xt モードでのみ有効です。
Sun MP C は、拡張 ANSI C コンパイラです。SPARC 共有メモリー付きマルチプロセッサマシン上で実行するコードをコンパイルすることができます。このプロセスは並列化と呼ばれています。コンパイル済みコードは、システム上の複数のプロセッサを用いて並列で実行することができます。
Sun WorkShop には、MP C の機能を使用するために必要なライセンスが含まれています。
この項では、MP C の概要と使用例を示すとともに、MP C で使用する環境変数、キーワード、プラグマ、オプションについて説明します。
MP C の使用例およびさらに詳しい参照情報については、/opt/SUNWspro/READMEs/ja/MPC.ps にある『MP C 技術白書』を参照してください。
MP C コンパイラによって、並列化しても安全であると判断されたループに対する並列コードが生成されます。通常、これらのループは、独立して実行可能な繰り返しを持っています。このようなループに対しては、繰り返しの実行される順番や、並列に実行するかどうかといったことなどは、実行結果に影響しません。すべてではありませんが、ほとんどのベクトル処理用ループはこのような種類のループです。
C では別名が存在する (複数の変数が同一の実体である、または実体を指す) 可能性があるため、並列化の安全性を判断することは困難です。コンパイラの作業を容易にするために、MP C には別名の情報をコンパイラに渡すためのプラグマおよびポインタ修飾子が用意されています。
以下の例では、MP C コンパイラを使用して、並列化を制御する様子を示しています。ターゲットのプログラムを並列化するためには、「-xautopar 」の -xautopar オプションを以下のように使用します。
% cc -fast -xO4 -xautopar example.c -o example
これを実行すると、example という実行可能ファイルが生成されます。ユーザーは通常の方法でこのファイルを実行することができます。
マルチプロセッサ上で実行する場合には、以下のように環境変数を設定することが必要になります。
% setenv PARALLEL 2
この設定によって、プログラムが 2 個のスレッド上で実行されるようになります。ターゲットのマシンに複数の CPU が装備されていれば、この設定によって、それぞれのスレッドがそれぞれの CPU にマップされます。
% example
プログラムを実行すると、2 個のスレッドが生成され、各スレッド上でプログラムの並列化された部分が実行されるようになります。
MP C ではキーワード _Restrict を使用することができます。詳細については、「_Restrict」の項を参照してください。
すでに述べたように、並列化の効果がどれだけあるかをコンパイラだけで決めるには、情報が不十分なことがあります。MP C では、プラグマをサポートしており、コンパイラだけでは不可能なループの並列化を効率よく実行することができます。
直列プラグマには 2 通りあり、どちらも for ループに適用されます。
#pragma MP serial_loop
#pragma MP serial_loop_nested
#pragma MP serial_loop
serial_loop プラグマによって、次に存在する for ループを暗黙的または自動的に並列化しないことが指示されます。
#pragma MP serial_loop_nested
serial_loop_nested プラグマによって、次にある for ループ、およびその for ループの中で入れ子になっている for ループを暗黙的または自動的に並列化しないことが指示されます。なお、serial_loop_nested のスコープは、このプラグマが適用されるループの範囲を越えることはありません。
並列プラグマは 1 つだけあります。
#pragma MP taskloop (オプション)
MP taskloop プラグマは、オプションとして、以下の引数を取ることができます。
maxcpus (<CPU 数>)
private (<スレッド固有変数リスト>)
shared (<共有変数リスト>)
readonly (<読み取り専用変数リスト>)
storeback (<ストアバック変数リスト>)
savelast
reduction (<縮約変数リスト>)
schedtype (<スケジューリング型>)
MP taskloop プラグマ 1 つに対して指定できるオプションは 1 つだけです。ただし、複数のプラグマの効果を重ねて、ソースコード内の現在のブロックにある次の for ループに適用することができます。
#pragma MP taskloop maxcpus(4) #pragma MP taskloop shared(a,b) #pragma MP taskloop storeback(x)
これらのオプションは、for ループの前に複数回指定できます。オプションが衝突を起こす場合には、コンパイラによって警告メッセージが出力されます。
MP taskloop プラグマは、現在のブロック内にある次の for ループに適用されます。MP C によって並列化された for ループに入れ子は存在しません。
MP taskloop プラグマによって、ループを並列化するように指示されます。
不規則なフロー制御や、一定しない増分による繰り返しを持ったループに対しては、正当な並列化を実行できません。たとえば、setjmp、longjmp、exit、abort、return、goto、ラベル、break を含んだ for ループは並列化に適しません。
特に重要なこととして、繰り返し間の依存性を持った for ループでも、明示的に並列化できる点に注意してください。すなわち、このようなループに対して MP taskloop プラグマが指定されていると、for ループが並列化に適していないと判断されない限り、単にこの指示に従ってコンパイルを実行してしまいます。このような明示的な並列化を行なった場合は、不正確な結果が発生しないかを確認してください。
1 つのループに対して serial_loop または serial_loop_nested と taskloop の両方のプラグマが指定されている場合には、最後の指定が優先的に使用されます。
#pragma MP serial_loop_nested for (i=0; i<100; i++) { # pragma MP taskloop for (j=0; j<1000; j++) { ... } }
この例では、i ループは並列化されませんが、j ループは並列化が可能です。
#pragma MP taskloop maxcpus (<プロセッサの数>) は、指定が可能であれば、現在のループに対して使用される CPU の数を指定します。
maxcpus に指定する値は正の整数でなければなりません。maxcpus が 1 であれば、指定されたループは直列に実行されます。なお、maxcpus を 1 に指定した場合には、doserial 指令をしたことと同等になる点に注意してください。また、maxcpus の値か PARALLEL 環境変数のどちらか小さい方の値が使用されます。環境変数 PARALLEL が指定されていない場合には、この値に 1 が指定されているものとして扱われます。
1 つの for ループに複数の maxcpus プラグマが指定されている場合には、最後に指定された値が優先的に使用されます。
ループに使用される変数は、「スレッド固有」、「共有」、「縮約」または「読み取り専用」のどれかに分類されます。1 つの変数は、これらの種類のうち 1 つにのみ属します。 変数の種類を縮約または読み取り専用にするには、明示的にプラグマで指示しなければなりません。詳しくは、「縮約変数」および 「読み取り専用変数」を参照してください。変数を「スレッド固有」または「共有」にするには明示的にプラグマを使用するか、または以下のスコープの規則にもとづいて決まります。
スレッド固有変数は、for ループのある繰り返しを処理するためにそれぞれの CPU が専用に使用する値を保持します。別の言い方をすれば、for ループのある繰り返しでスレッド固有変数に割り当てられた値は、そのループの別の繰り返しを処理している CPU からは見えません。これに対して共有変数は、このような特性を持っていません。共有変数とは、現在のループ繰り返しを処理しているすべての CPU から現在の値にアクセスできる変数のことです。ループのある繰り返しを処理している 1 つの CPU が共有変数に代入した値は、そのループの別の繰り返しを処理している CPU からでも見ることができます。共有変数を参照しているループを #pragma MP taskloop 指令によって明示的に並列化する場合には、値の共有によって正確性に問題が起きないことを確認しなければなりません (競合条件の確認など)。明示的に並列化されたループの共有変数へのアクセスおよび更新では、コンパイラによる同期はとられません。
明示的に並列化されたループの解析において、変数がスレッド固有と共有のどちらであるかを決定するために、以下の「デフォルトのスコープの規則」が使用されます。
変数がプラグマによって明示的に分類されていない場合には、その変数がポインタまたは配列として宣言されていて、かつループ内では配列構文を使用して参照している限り、その変数はデフォルトで共有変数として分類されます。
ループのインデックス変数は常にスレッド固有変数として扱われ、また常に書き戻しの対象として扱われます。
明示的に並列化された for ループ内のすべての変数を、共有、スレッド固有、縮約、または読み取り専用として明示的に指定し、デフォルトのスコープの規則が適用されないようにすることを、強くお勧めします。
コンパイラは、共有変数に対するアクセスの同期を一切実行しないので、たとえば、配列参照を含んだループに対して MP taskloop プラグマを使用する前には、十分な考察が必要になります。このように明示的に並列化されたループで、繰り返し間でのデータ依存性がある場合には、並列実行を行うと正しい結果を得られないことがあります。コンパイラによって、このような潜在的な問題を検出し、警告メッセージを出力することもできますが、一般的にこれを検出することは非常に困難です。なお、共有変数に対する潜在的な問題を持ったループでも、明示的に並列化を指示されると、コンパイラはこの指示に従います。
#pragma MP taskloop private (<スレッド固有変数のリスト>) は、現在のループでスレッド固有変数として扱われる必要のあるすべての変数を指定します。ループで使用されている別の変数は、それ自体が明確に共有、読み取り専用、ストアバック、または縮約であることが指定されていない限り、デフォルトのスコープの規則に従って、共有またはスレッド固有のどちらかに分類されます。
スレッド固有変数とは、それ自体の値がループのある繰り返しを処理する CPU 専用になっている変数のことです。別の言い方をすれば、ループのある繰り返しを処理している CPU によってスレッド固有変数に代入された値は、そのループの別の繰り返しを処理している CPU から見ることはできません。スレッド固有変数には、ループの繰り返しの開始時に初期値は代入されず、繰り返し内で最初に使用される前に、その繰り返し内で値が代入されなければなりません。値が設定される前にその値を参照するように明確に宣言されたスレッド固有変数を持つループを実行すると、その動作は保証されません。
#pragma MP taskloop (<共有変数のリスト>) は、現在のループで共有変数として扱われる必要のあるすべての変数を指定します。ループで使用されている別の変数は、それ自体が明確にスレッド固有、読み取り専用、ストアバック、または縮約であることが指定されていない限り、デフォルトのスコープの規則に従って、共有またはスレッド固有のどちらかに分類されます。
共有変数とは、ある for ループの繰り返しを処理しているすべての CPU から現在の値を見ることのできる変数のことです。ループのある繰り返しを処理している CPU が共有変数に代入した値は、そのループの別の繰り返しを処理している CPU からでも見ることができます。
#pragma MP taskloop readonly (<読み取り専用変数リスト>) は、現在のループで読み取り変数として扱われる必要のあるすべての変数を指定します。
読み取り専用変数とは、共有変数の特殊なクラスのことで、ループのどの繰り返しでも、その値を変更できません。変数を読み取り専用として指定すると、ループの繰り返しを処理しているそれぞれの CPU に対して、個々に変数値のコピーが使用されます。
#pragma MP taskloop storeback (<ストアバック変数リスト>) は、現在のループでストアバック変数として扱われる必要のあるすべての変数を指定します。
ストアバック変数とは、ループの中で変数値が計算され、その値がループの終了後に使用される変数のことです。ループの最後の繰り返しにおけるストアバック変数の値が、ループの終了後に利用可能になります。このような変数は、その変数が明示的な宣言やデフォルトのスコープ規則によってスレッド固有変数となっている場合には、この指令を使用して明示的にストアバック変数として宣言するとよいでしょう。
なお、ストアバック変数に対する最終的な書き戻し操作は、明示的に並列化されたループの最後の繰り返しにおいて、その中で実際に値が変更されたかどうかには関係なく実行される点に注意してください。すなわち、ループの最後の繰り返しを処理する CPU と、ストアバック変数の最終的な値を保持している CPU とは、異なる可能性があります。
#pragma MP taskloop private(x) #pragma MP taskloop storeback(x) for (i=1; i <=n; i++) { if (...) { x =... } } printf ("%d", x);
上述の例では、printf() 呼び出しによって出力されるストアバック変数 x の値は、i のループを直列に実行した場合の出力値とは異なる可能性があります。なぜならば、明示的に並列化されたループでは、ループの最後の繰り返し (すなわち i==n のとき) を処理し、x に対してストアバック操作を行う CPU は、現在最後に更新された x の値を保持する CPU とは同じでないことがあるからです。このような潜在的な問題に対し、コンパイラは警告メッセージを出力します。
明示的に並列化されたループでは、配列として参照される変数をストアバック変数とは扱いません。したがって、このような変数にストアバック処理が必要な場合 (たとえば、配列として参照される変数がスレッド固有変数として宣言されている場合) には、その変数を <ストアバック変数リスト> に含める必要があります。
このプラグマによって、ループ内のすべてのスレッド固有変数がストアバック変数として扱われます。このプラグマの構文を以下に示します。
#pragma MP taskloop savelast
各変数をストアバック変数として宣言するときには、それぞれのスレッド固有変数をリストするよりも、この形式が便利であることがよくあります。
リストにあるすべての変数は、そのループに対しての縮約変数として扱われます。縮約変数とは、ループのある繰り返しを処理している個々の CPU によって、その値を部分的に計算され、最終値がすべての部分値から計算される変数のことをいいます。縮約変数リストにより、そのループが縮約ループであることをコンパイラに示し、適切な並列縮約用のコードを生成できるようにします。以下の例を考えてみましょう。
#pragma MP taskloop reduction(x) for (i=0; i<n; i++) { x = x + a[i]; }
ここでは、変数 x が (和の) 縮約変数であり、i ループが (和の) 縮約ループになっています。
MP C コンパイラには、指定されたループのスケジューリングを戦略的に制御するために、taskloop プラグマと同時に使用するいくつかのプラグマが用意されています。このプラグマの構文を以下に示します。
#pragma MP taskloop schedtype ( <スケジューリング型>)
このプラグマによって、並列化されたループをスケジュールするための <スケジューリング型> を指定することができます。<スケジューリング型> には、以下のいずれかを指定できます。
static
静的スケジューリングでは、ループ内のすべての繰り返しが、そのループを処理するすべての CPU に均等に配分されます。
#pragma MP taskloop maxcpus(4) #pragma MP taskloop schedtype(static) for (i=0; i<1000; i++) { ... }
上述の例では、4 個の CPU が、ループの繰り返しを 250 ずつ処理します。
self [<チャンクサイズ>]
自己スケジューリングでは、ループのすべての繰り返しが処理されるまで、固定された回数の繰り返し <チャンクサイズ> を、そのループを処理するそれぞれの CPU で処理します。<チャンクサイズ> は省略可能で、使用するチャンクサイズを指定します。 <チャンクサイズ> は、正の整数の定数か、もしくは整数型の変数でなければなりません。変数が指定された場合、<チャンクサイズ> はそのループを開始する前に、その変数が正の整数を保持していなければなりません。このオプションの引数が指定されていない場合、もしくは、この値が正の整数ではない場合、チャンクサイズはコンパイラによって決められます。
#pragma MP taskloop maxcpus(4) #pragma MP taskloop schedtype(self(120)) for (i=0; i<1000; i++) { ... }
上述の例では、ループを処理するそれぞれの CPU に割り当てられる繰り返し数は、割り当て順に以下のようになります。
120, 120, 120, 120, 120, 120, 120, 120, 40.
gss [<最小チャンクサイズ>]
ガイド付き自己スケジューリング (GSS) では、ループのすべての繰り返しが処理されるまで、可変な繰り返し回数 (チャンクサイズ) を、そのループを処理するそれぞれの CPU で処理します。オプションの <最小チャンクサイズ> 引数によって、可変チャンクサイズがそれぞれ最低でも <最小チャンクサイズ> になるように指示されます。 <最小チャンクサイズ> は、正の整数の定数か、もしくは整数型の変数でなければなりません。変数が指定された場合、<最小チャンクサイズ> はそのループを開始する前に、その変数が正の整数を保持していなければなりません。このオプションの引数が指定されていない場合、もしくは、この値が正の整数ではない場合には、チャンクサイズはコンパイラによって決められます。
#pragma MP taskloop maxcpus(4) #pragma MP taskloop schedtype(gss(10)) for (i=0; i<1000; i++) { ... }
上述の例では、ループを処理するそれぞれの CPU に割り当てられる繰り返し数は、割り当て順に以下のようになります。
250, 188, 141, 106, 79, 59, 45, 33, 25, 19, 14, 11, 10, 10, 10.
factoring [<最小チャンクサイズ>]
ファクタリング・スケジューリングでは、ループのすべての繰り返しが処理されるまで、可変数の繰り返し (チャンクサイズ) を、そのループを処理するそれぞれの CPU で処理します。オプションの <最小チャンクサイズ> 引数によって、可変チャンクサイズが、それぞれ最低でも <最小チャンクサイズ> になるように指示されます。<最小チャンクサイズ> は、正の整数の定数か、もしくは整数型の変数でなければなりません。変数が指定された場合、<最小チャンクサイズ> はそのループを開始する前に、その変数が正の整数を保持していなければなりません。このオプションの引数が指定されていない場合、もしくは、この値が正の整数ではない場合には、チャンクサイズはコンパイラによって決められます。
#pragma MP taskloop maxcpus(4) #pragma MP taskloop schedtype(factoring(10)) for (i=0; i<1000; i++) { ... }
上述の例では、ループを処理するそれぞれの CPU に割り当てられる繰り返し数は、割り当て順に解釈すると以下のようになります。
125, 125, 125, 125, 62, 62, 62, 62, 32, 32, 32, 32, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10.
MP C では次のコンパイラオプションを使用することができます。これらのオプションの詳細は、第 2 章「cc コンパイラオプション」で説明してあります。
-xautopar (「-xautopar 」)
-xdepend (「-xdepend」)
-xexplicitpar (「-xexplicitpar」)
-xloopinfo (「-xloopinfo」)
-xparallel (「-xparallel」)
-xreduction (「-xreduction」)
-xrestrict = [<関数 1>, ... ,<関数 n>] (「-xrestrict=f 」)
-xvpara (「-xvpara」)
-Zlp (「-Zlp」)