Sun Studio 12: C ユーザーズガイド

付録 D C99 でサポートされている機能

この付録では、ISO/IEC 9899:1999、Programming Language - C の規格でサポートされている機能について説明します。

-xc99 フラグは、実装されている機能をコンパイラで認識させるかどうかを設定します。 -xc99 の構文の詳細については、「B.2.73 -xc99[= o]」を参照してください。


注 –

コンパイラは、デフォルトでは以降で説明している C99 の機能をサポートしていますが、Solaris が提供する標準のヘッダーファイル (/usr/include にある) は、1999 ISO/IEC C 規格にまだ準拠していません。エラーメッセージが生成される場合は、-xc99=none を指定して、ヘッダー用に 1990 ISO/IEC C 規格を使用してみてください。


D.1 説明と使用例

この付録では、サポートされている機能のうちの一部を詳細に説明し、使用例を示します。

D.1.1 浮動小数点評価における精度

5.2.4.2.2 項 浮動小数点型 <float.h> の特性

浮動小数点オペランドを持つ演算の値、および通常の算術変換および浮動小数点定数両方の影響を受ける値は、その型が必要とするより大きくすることが可能な範囲および精度を持つ形式で評価されます。使用する評価形式は、 FLT_EVAL_METHOD の実装定義値によって決まります。

表 D–1 FLT_EVAL_METHOD の値

値 

意味 

-1 

判定不能 

コンパイラは、すべての演算および定数を正確にその型の範囲と精度で評価します。 

コンパイラは、float および double 型の演算および定数を double 型の範囲および精度で評価します。long double 型の演算および定数は long double 型の範囲と精度で評価します。 

コンパイラは、すべての演算および定数を long double 型の範囲と精度で評価します。 

SPARC アーキテクチャーで float.h をインクルードすると、デフォルトでは、FLT_EVAL_METHOD0 に展開され、すべての浮動小数点式はその型に従って評価されます。

x86 アーキテクチャーで float.h をインクルードすると、デフォルトでは、 FLT_EVAL_METHOD-1 に展開され (-xarch=sse2 または -xarch=amd64 の場合を除く)、 すべての浮動小数点定数式はその型として、ほかのすべての浮動小数点式は long double として評価されます。

-flteval=2 を指定して、float.h をインクルードすると、FLT_EVAL_METHOD2 に展開され、すべての浮動小数点式は long double として評価されます。詳細については、「B.2.20 -flteval[={any|2}]」を参照してください。

x86 アーキテクチャーで -xarch=sse2 または -xarch=amd64 を指定して、float.h をインクルードすると、FLT_EVAL_METHOD は 0 に展開され、すべての浮動小数点式はその型に従って評価されます。

浮動小数点式が double として評価されても、-Xt オプションが FLT_EVAL_METHOD の展開内容に影響することはありません。詳細については、「B.2.63 -X[c|a|t|s]」を参照してください。

-fsingle を指定すると、浮動小数点式が単精度で評価されます。詳細については、「B.2.27 -fsingleを参照してください。

x86 アーキテクチャーで -xarch=sse2 または -xarch=amd64 とともに -fprecision を指定して、float.h をインクルードすると、FLT_EVAL_METHOD-1 に展開されます。

D.1.2 C99 のキーワード

6.4.1 キーワード

C99 の規格では、次のキーワードが追加されました。-xc99=none を指定した場合に、これらのキーワードを識別子として使用すると、コンパイラは警告を生成します。-xc99=none を指定していない場合は、これらのキーワードが識別子として使用されているときに、コンパイラがコンテキストに応じて警告またはエラーメッセージを生成します。

D.1.2.1 restrict キーワードの使用

restrict で修飾されたポインタを使用してオブジェクトにアクセスするには、その オブジェクトへのすべてのアクセスで、直接的または間接的にそのポインタの値を使用する必要があります。ほかの方法によってそのオブジェクトにアクセスすると、定義されていない動作が発生する可能性があります。restrict 修飾子は、コンパイラで最適化を行うための想定を可能にするために使用します。

restrict 修飾子を効果的に使用する例および方法については、「3.8.2 制限付きポインタ」を参照してください。

D.1.3 __func__ のサポート

6.4.2.2 定義済み識別子

コンパイラで、定義済み識別子 __func__ がサポートされました。__func__ は、__func__ のあるコードでの現在の関数の名前を格納する文字配列として定義されています。

D.1.4 汎用文字名 (UCN)

6.4.3 項 汎用文字名

UCN では、C のソースで英字ばかりでなく、任意の文字を使用することができます。UCN の形式は次のとおりです。

UCN には、00A0 未満で 0024 ($)、0040 (@)、0060 (?) 以外の値、あるいは D800 〜 DFFF の範囲の値を指定できません。

識別子や文字定数、文字列リテラルで UCN を使用して、C の基本文字セットにはない文字を表すことができます。

UCN の \unnnnnnnn は、8 桁の短い識別子が nnnnnnnn の文字を表します (ISO/IEC 10646 で規定)。同様に、汎用文字名の \unnnn は、4 桁の短い識別子が nnnn (8 桁の短い識別子は 0000nnnn) の文字を表します。

D.1.5 // を使用したコードのコメント処理

6.4.9 コメント

// から復帰改行までのすべての複数バイト文字 (復帰改行そのものは除く) は、コメントとして処理されます。 ただし、文字定数、文字列リテラル、コメント内で // が使用されている場合は、コメントとして処理されません。

D.1.6 暗黙の int および暗黙の関数宣言の禁止

6.5.2.2 関数呼び出し

暗黙の宣言は、1990 C 規格の場合とは異なり、1999 C 規格では許可されなくなりました。以前のバージョンの C コンパイラでは、-v (冗長形式) を指定した場合にだけ、暗黙の定義についての警告メッセージが生成されていました。暗黙の定義についてのこれらのメッセージおよび新しい警告は、識別子が暗黙に int または関数として宣言されている場合は常に生成されます。

多数の警告メッセージが生成されることがあるため、この変更はたいていの場合はすぐにわかります。よくある原因としては、たとえば、printf では <stdio.h> をインクルードする必要があるように、使用する関数を宣言する適切なシステムヘッダーファイルをインクルードしていないことが考えられます。1990 C 規格に従って暗黙の宣言を許可するには、-xc99=none を指定します。

C コンパイラは、暗黙の関数宣言に対して警告を生成するようになりました。


example% cat test.c
void main()
{
  printf("Hello, world!\n");
}
example% cc test.c
"test.c", 3 行目:警告: 暗黙的な関数宣言: printf
example%

D.1.7 暗黙の int を使用した宣言

6.7.2 型指示子

少なくとも 1 つの型指示子を、各宣言の宣言指示子で指定します。「D.1.6 暗黙の int および暗黙の関数宣言の禁止」も参照してください。

暗黙の int 宣言を使用した場合は、C コンパイラは次の例のように警告を生成します。


example% more test.c
volatile i;
const foo()
{
  return i;
}
example% cc test.c
  "test.c",  1 行目: 警告: 明示的な型が与えられていません
  "test.c",  3 行目: 警告: 明示的な型が与えられていません
example%

D.1.8 柔軟な配列のメンバー

6.7.2.1 構造体および共用体の指示子

「struct hack」とも呼びます。構造体の最後のメンバーを、int foo[]; などのゼロ長の配列にすることができます。このような構造体は、malloc で割り当てられたメモリーにアクセスするためのヘッダーとして一般的に使用されます。

たとえば、struct s { int n; double d[]; } S; では、配列 d が不完全な配列型です。C コンパイラは、この S のメンバーのメモリーオフセットをカウントしません。つまり、sizeof(struct s)S.n のオフセットと同一になります。

d は、通常の配列メンバーと同様に、S.d[10] = 0; のように使用することができます。

C コンパイラが不完全な配列型をサポートしていない場合は、次の例の DynamicDouble のような構造体を定義および宣言します。


typedef struct { int n; double d[1]; ) DynamicDouble;

ここで、配列 d は不完全な配列型ではなく、1 つのメンバーを指定して宣言されています。

次に、ポインタdd を宣言してメモリーを割り当てます。


DynamicDouble *dd = malloc(sizeof(DynamicDouble)+(actual_size-1)*sizeof(double));

そのあとで、次のように S.n にオフセットのサイズを格納します。


dd->n = actual_size;

コンパイラが不完全な配列型をサポートしているため、1 つのメンバーを指定して配列を宣言することなく、同一の結果を得ることができます。


typedef struct { int n; double d[]; } DynamicDouble;

ここで、ポインタ dd を宣言し、前の場合と同様にメモリーを割り当てます。ただし、ここでは actual_size から 1 を引く必要はありません。


DynamicDouble *dd = malloc (sizeof(DynamicDouble) + (actual_size)*sizeof(double));

前の場合と同様に、次のように S.n にオフセットのサイズを格納します。


dd->n = actual_size;

D.1.9 べき等修飾子

6.7.3 型修飾子

同一の指示子と修飾子のリストで、直接または typedef により同一の修飾子が複数ある場合は、動作は型修飾子が 1 つだけの場合と同様になります。

C90 では、次のコードではエラーが発生します。


%example cat test.c

const const int a;

int main(void) {
  return(0);
}

%example cc -xc99=none test.c
"test.c",  1 行目: 型の組み合わせが不適切です

ただし、C99 では、C コンパイラで複数の修飾子を使用できます。


%example cc -xc99 test.c
%example

D.1.10 inline 関数

6.7.4 関数指示子

C99 では、関数指示子 inline が追加されました。inline は、内部および外部リンクのある両方の関数で完全に機能します。インライン関数の定義および extern インライン関数が、1999 C ISO 規格の仕様どおりに機能するようになっています。

インライン関数定義とは、キーワード inline を使い、static または extern キーワードなしで定義された関数です。ソース (またはインクルードファイル) 内に含まれるあらゆるプロトタイプにも、static または extern キーワードなしでキーワード inline が含まれます。

インライン関数定義は、関数に外部定義を提供しません。インライン定義を含むソースファイルに現れる関数呼び出しはどれも、呼び出し位置で関数定義をインライン化するか、外部定義された関数を参照することによって実現されます。

最適化による利点を考慮した結果、コンパイラは、そうすることにメリットがあると判断した場合にのみ、インライン定義への呼び出しをインライン化します。これ以外の場合は、外部関数に対する呼び出しが行われます。このため、インライン定義を含むプログラムは、extern 関数定義を含むオブジェクトファイルとリンクさせることを推奨します。

関数定義とともに (あるいは関数定義を継続するファイルの任意のプロトタイプ上で) extern および inline 両方のキーワードを使用すると、外部関数がそのオブジェクトファイル内に定義されます。C++ リンクと互換性を維持するため、extern インライン関数の定義を複数含むオブジェクトをリンクする場合、リンカーは、そうした関数のうちの 1 つだけ選択し、すべての外部参照を実現します。

規格に適合した動作を実現するには、最新のコンパイラを使って古いコードをコンパイルする必要があります。ただし、古い C および C++ バイナリ (C/C++ 5.6 より前) で extern インライン関数を使用していて、その古いバイナリの動作を変更することなく、新しい C および C++ バイナリとリンクする場合は、 -features=no%extinl を指定します。

D.1.11 配列宣言子で使用可能な Static およびそのほかの型修飾子

6.7.5.2 配列宣言子

関数宣言子内のパラメータの配列宣言子で static キーワードを使用することが可能になりました。この場合は、コンパイラが、宣言する関数に多数の要素が引き渡されることを少なくとも認識することができます。これにより、オプティマイザで従来は不可能だった想定が可能になります。

C コンパイラは、配列パラメータをポインタに変換します。したがって、void foo(int a[])void foo(int *a) と同義になります。

void foo(int * restrict a); などの型修飾子を指定すると、C コンパイラはそれを配列文 void foo(int a[restrict]); で表現します。これは、実質的には制限付きポインタを宣言するのと同義です。

C コンパイラは、配列サイズに関する情報を保持するためにも static 修飾子を使用します。たとえば、void foo(int a[10]) を指定した場合でも、コンパイラはこれを void foo(int *a) と表現します。ポインタが NULL ではなく、少なくとも 10 個の要素を持つ整数配列へのポインタであることをコンパイラに認識させるには、void foo(int a[static 10]) のように static 修飾子を使用します。

D.1.12 可変長配列 (VLA)

6.7.5.2 配列宣言子

VLA は、alloca 関数を呼び出した場合と同様に、スタックに割り当てられます。VLA の有効期間は、有効範囲に関係なく、alloca の呼び出しによりスタックに割り当てられたデータと同様に、関数から復帰するまでです。割り当てられた領域は、VLA が割り当てられた関数から復帰してスタックが解放されるときに同時に解放されます。

可変長配列では、一部の制約がまだ有効になっていません。制約に違反すると、定義されていない結果になります。


#include <stdio.h>
void foo(int);

int main(void) {
  foo(4);
  return(0);
}

void foo (int n) {
  int i;
  int a[n];
  for (i = 0; i < n; i++)
    a[i] = n-i;
  for (i = n-1; i >= 0; i--)
    printf("a[%d] = %d\n", i, a[i]);
}

example% cc test.c
example% a.out
a[3] = 1
a[2] = 2
a[1] = 3
a[0] = 4

D.1.13 指示付きの初期化子

6.7.8 初期化

指示付き初期化子は、数値プログラミングで一般的なスパース配列を初期化する仕組みです。

指示付き初期化子によって、システムプログラミングで一般的なスパース構造体を初期化したり、先頭メンバーであるかどうかに関係なく、任意のメンバーを使って共用体を初期化したりできます。

例を挙げて考えてみます。最初の例は、指示付き初期化子を使って配列を初期化する方法を示しています。


 enum { first, second, third };
          const char *nm[] = {
          [third] = "third member",
          [first] = "first member",
          [second] = "second member",
          };

次の例は、指示付き初期化子を使用して struct オブジェクトのフィールドを初期化する方法を示しています。


division_t result = { .quot = 2, .rem = -1 };

次の例は、指示付き初期化子を使用して、これ以外の方法では誤解を生むおそれがある複雑な構造体を初期化する方法を示しています。


 struct { int z[3], count; } w[] = { [0].z = {1}, [1].z[0] = 2 };

1 つの指示子で両端から配列を作成することができます。


int z[MAX] = {1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0};

MAX が 10 より大きい場合、この配列の中央には値ゼロの要素が含まれます。MAX が 10 より小さい場合、最初の 5 つの初期化子が提供する値の一部は、2 つ目の 5 つの値によって置き換えられます。

共用体のすべてのメンバーを初期化することができます。


union { int i; float f;} data = { .f = 3.2 };

D.1.14 型宣言とコードの混在

6.8.2 複合文

C コンパイラで、次の例のように型宣言と実行可能コードを混在させることが可能になりました。


#include <stdio.h>

int main(void){
  int num1 = 3;
  printf("%d\n", num1);

  int num2 = 10;
  printf("%d\n", num2);
  return(0);
}

D.1.15 for ループ文での宣言

6.8.5 繰り返し文

C コンパイラで、for ループ文の最初の式として、型宣言を使用できるようになりました。


for (int i=0; i<10; i++){ //loop body };

for ループの初期化文で宣言した変数の有効範囲は、ループ全体になります (制御式と繰り返し式を含む)。

D.1.16 引数の個数が可変するマクロ

6.10.3 マクロ置換

C コンパイラで、次の形式の #define プリプロセッサ指令を使用することができます。


#define identifier (...) replacement_list
#define identifier (identifier_list, ...) replacement_list

マクロ定義で identifier_list が省略符号で終わる場合は、マクロ定義でのパラメータよりも呼び出しの引数の方が多いことを示します (省略符号を除く)。それ以外の場合は、マクロ定義でのパラメータ数 (プリプロセッサトークンのない引数を含む) が引数の個数と一致します。引数に省略符号表記を使用する #define のプリプロセッサ 指令の置換リストで、識別子 __VA_ARGS__ を使用します。次のコードは、マクロの可変引数リスト機能の例です。


#define debug(...) fprintf(stderr, __VA_ARGS__)
#define showlist(...) puts(#__VA_ARGS__)
#define report(test, ...) ((test)?puts(#test):\
                        printf(__VA_ARGS__))
debug(“Flag”);
debug(“X = %d\n”,x);
showlist(The first, second, and third items.);
report(x>y, “x is %d but y is %d”, x, y);

この結果は、次のようになります。


fprintf(stderr, “Flag”);
fprintf(stderr, “X = %d\n”, x);
puts(“The first, second, and third items.”);
((x>y)?puts(“x>y”):printf(“x is %d but y is %d”, x, y));

D.1.17 _Pragma

6.10.9 プラグマ演算子

_Pragma ( string-literal ) という形式の単項演算子の式は、次のように処理されます。

生成されたプリプロセッサトークンのシーケンスは、プラグマの指令でのプリプロセッサトークンと同様に処理されます。

単項演算子の式にある元の 4 つのプリプロセッサトークンは削除されます。

_Pragma は、#pragma と比較して、マクロ定義で使用可能であるという利点があります。

_Pragma("string") は、#pragma 文字列とまったく同一になります。次の例を考えてみましょう。最初の例は、プリプロセッサの使用前のソースコードです。 次の例は、プリプロセッサの使用後のソースコードです。


example% cat test.c

#include <omp.h>
#include <stdio.h>

#define Pragma(x) _Pragma(#x)
#define OMP(directive) Pragma(omp directive)

void main()
{
  omp_set_dynamic(0);
  omp_set_num_threads(2);
  OMP(parallel)
  {
  printf("Hello!\n");
  }
}

example% cc test.c -P -xopenmp -x03
example% cat test.i

次は、プリプロセッサ終了後のソースコードです。


void main()
{
  omp_set_dynamic(0);
  omp_set_num_threads(2);
  # pragma omp parallel
  {
    printf("Hellow!\n");
  }
}

example% cc test.c -xopenmp -->
example% ./a.out
Hello!
Hello!
example%