表紙 / 自作ソフト / 日記 / 宝箱 / サイト情報 / 検索
一般 / 新C言語 / 駄文

プログラミング言語 C の新機能


←前頁へ◎表紙へ次頁へ→
 
 

1. 概略
1.1 はじめに
1.2 新しい予約語
1.3 新しいヘッダファイル
2. C プリプロセッサ
2.1 新しいコメント表記方法
2.2 空引数を許された関数型マクロ呼出し
2.3 可変個数引数を持つマクロ定義
2.4 あらかじめ定義されているマクロ名
2.5 プログラミング言語 C 標準プラグマ
2.6 単項演算子 _Pragma
2.7 プリプロセッサ式における整数型
2.8 文字列定数とワイド文字列定数の結合
3. 字句
3.1 ユニバーサルキャラクタ名
3.2 拡張された識別名使用文字
3.3 浮動小数点定数の 16 進数表記
4. 配列
4.1 可変長配列
4.2 構造体中の 0 長配列メンバ
4.3 配列要素中の記憶/型修飾子
5. 整数型
5.1 _Bool 型
5.2 long long int 型
5.3 long long int 型の定数
5.4 整数除算
6. 複素数型
6.1 _Complex 型
6.2 _Imaginary 型
6.3 複素数に関する四則演算
7. 文法一般
7.1 暗黙の関数宣言
7.2 宣言時の暗黙の型
7.3 前定義識別名 __func__
7.4 enum 宣言での余分なカンマ
7.5 inline 関数定義
7.6 restrict ポインタ
7.7 変数宣言と実行コードの位置関係
7.8 指示付きの初期化子 (Designated Initializer)
7.9 複合リテラル (Compound Literal)
7.10 選択文と反復文のブロック化
8. 標準ライブラリ(拡張)
8.1 ctype.h
8.2 float.h
8.3 math.h
8.4 stdarg.h
8.5 stdio.h
8.6 stdlib.h
8.7 wchar.h
8.8 wctype.h
9. 標準ライブラリ(新規)
9.1 complex.h: 複素数
9.2 fenv.h: 浮動小数点環境
9.3 inttypes.h
9.4 stdbool.h
9.5 stdint.h
9.6 tgmath.h: 型総称数学関数
A. 付録
A.1 strftime 書式指定子
A.2 printf の書式

7. 文法一般
 この章では C 言語に新たに加えられた指示付きの初期化子や複合リテラルといった文法や、暗黙時の宣言に関する変更といった、変更された文法について説明します。

7.1 暗黙の関数宣言
 現在の C 言語の規格では、ある関数から他の関数を呼び出す時、とくにその関数のプロトタイプを宣言しなくても使用することができました。例えば stdio.h を読み込まずに、printf を使用するといったことです。しかし、今度の C 言語ではそのような場合、その関数の呼び出しは規格上未定義の動作となります。ただ実際には多くの処理系では警告を出してコンパイルするよう実装されることでしょう。この辺の機能は C++ に近くなっていますね。

7.2 宣言時の暗黙の型
 手続き型言語では、一般的に変数や関数などを宣言して使用する必要があります。もちろん C 言語も例外ではありません。しかし C 言語では特に型名を指定せずに変数などを宣言することができました。その場合、型がない変数が宣言されたというわけではなく、自動的に int 型として処理されました。例を挙げれば、次のようなプログラムです。

func(p1,p2)
{
 const three=3;
 return (p1+p2)*three;
}

この場合、関数 func の戻り型は int 型、p1, p2, three の型は int 型です。 このような記述ができるということが C 言語の性質をよくあらわしています。 しかしながら、新しい C 言語では、このような暗黙の int 型の使用は規格上できないことになりました。したがって、常に次のように書く必要があります。

int func(int p1,int p2)
{
 const int three=3;
 return (p1+p2)*three;
}


7.3 前定義識別名 __func__
 お待たせしました。待望の機能の一つが入りました。それが前定義識別名 __func__ です。
 さてさて、今まで皆さんはプログラムをどのようにデバッグをしてきましたか?何?デバッガを使っていた?そのような豪華な環境にある人は、この機能のありがたさを実感することが難しいかもしれません。
 プログラムのデバッグにあたってまず大事なことは、いったいプログラムはどの部分を走っているのかということを知ることです。例えば、条件分岐をしたとき想定したコードの方をちゃんと通ったのかを知る必要があります。そのようなときには printf を埋め込んだりして、正しい方向にプログラムが実行しているかを検査する必要がありました。しかし、printf を埋め込んだ部分が多くなってくると、だんだんどこの printf の出力であるのかがわからなくなるといった問題がありました。
 前定義識別名はこのような時効果を発揮します。例えば、今回導入された前定義識別名と可変長マクロ機能を組み合わせると次のようなことができます。

前定義識別名 __func__ 使用例
/*
 * 前定義識別名 __func__ 使用例 (c)1999 seclan
 * ver1.00 1999/05/16 最初のバージョン
 */
#define dbg(...) \
 (printf("%s %u @%s:",__FILE__,__LINE__,__func__), \
 printf(" "__VA_ARGS__))

int foo_bar(int i)
{
  //static const char __func__[] = "foo_bar"; //(1)
  if(i != 3){
    dbg("i=%d", i);
    i *= i;
  }
  return i;
}

 今度の C 言語では、この例の (1) で示しているように、各関数の入り口で __func__ という識別名があたかも自動的に宣言されたかのようになります。したがって、この関数のスコープ中 __func__ を参照することができます。__func__ はその関数の関数名が入ります。この場合、関数名が foo_bar なので __func__ は "foo_bar" をあらわすことになります。例の中では i が 3 ではないとデバッグコードを実行します。今 i が 5 であると考えると、「test.c 13 @foo_bar: i=5」のような表示がなされます。
 このように、今回の C 言語の機能を使うことにより、デバッグコードをより簡単に美しく記述できるようになることがわかりますね。

7.4 enum 宣言での余分なカンマ
 配列を初期化する時は、int i[ ] = { 1, 2, 3, } のように最後の余分なカンマは許されていました。しかし、enum 宣言では enum { a, b, c,} のように最後に余分なカンマを入れるとエラーになっていました。今回の C 言語では、このような enum 宣言での余分なカンマが許されるようになりました。

7.5 inline 関数定義
 C++ にもある機能が C にも導入されました。その機能は inline 関数定義の機能です。今までの C 言語では、関数のように記述でき、しかも高速に呼び出せるようにしたい場合には、関数型マクロを使用する必要がありました。しかし、関数型マクロでは型検査ができない上、使い方にはよっては予測できない結果を導くこともありました。
 今回導入される inline 機能を使うと、型検査もされ、さらに通常の関数と同じ評価がなされ、かつ高速に実行できる関数を定義することができます。使い方は、ただ単に関数を inline キーワードを使って修飾するだけです。例えば、inline void swap(int *a1, int *a2){...} です。ただし外部リンケージを持つ関数に対しては、inline 付きの関数宣言を行ったらその定義を同じ翻訳単位中でしなくてはならない、修正可能な静的記憶寿命を持つ定義をふくんではならない、内部リンケージを持つ識別名への参照をふくんではならない、という制約があります。また main 関数は inline 修飾をすることができません。
 inline 定義はコンパイラに対するヒントであり、コンパイラは必ずしも高速呼び出しコードを生成するとは限りません。

7.6 restrict ポインタ
 C 言語は高級言語としてはポインタをかなり自由に使える言語です。その結果、プログラマがポインタを上手に使えば早いプログラムを書けるようになりました。しかしその一方で、ポインタの別名を簡単に作ることができるようになってしまったので、コンパイラがデータ解析や最適化コードを生成する時の障害になってきました。例えば、次の例を見てください。


//(1)
void sumup(int n, int *array1, int *array2)
{
  assert((n % 4)== 0);
  for(int i = 0; i < n; i++) array1[i] += array2[i];
}

//(2)
trymore:
 r1 ← array1[i+0]:[i+1]:[i+2]:[i+3]
 r2 ← array2[i+0]:[i+1]:[i+2]:[i+3]
 r1 += r2
 array1[i+0]:[i+1]:[i+2]:[i+3] ← r1
 i += 4
 if( i < n ) goto trymore

 (1)の部分のsumup 関数は array1 に array2 の内容を n 個分足し込む関数です。ただしここでこのプログラム中では n の値が 4 の倍数で array1 と array2 は同じ内容を指していない呼び出ししかしないと仮定できることとします。するとそれに対応して SIMD 命令を使用できる CPU 上のアセンブラのコードのループ部分は (2) のように最適化することができます。まず array1 の 4 要素分を r1 レジスタに格納します。そして同様に array2 の 4 要素分を r2 レジスタに入れ、それを r1 と加算して、結果となる r1 を array1 に書き戻します。たった一命例で配列 4 要素分の加算ができ、また結果として n/4 回のループで計算できるのでこのコードは効率的です。
 しかし現実にはコンパイラはこのような効率的なコードを生成することはできませんでした。なぜならば、コンパイラは array1 と array2 が同じ内容を指していないと仮定してよいことはまったく知りません。したがって、(3) のような呼び出しにも対応する (2) よりも劣るコード (4) を生成する必要があります。(4) は配列の要素一つずつ加算していくので n 回のループと加算が行われることになります。


//(3)
  int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, }
  sumup(4, array+2, array);

//(4)
trymore:
 r1 ← array1[i]
 r2 ← array2[i]
 r1 += r2
 array1[i] ← r1 
 i ++
 if( i < n ) goto trymore

(3) の呼び出し後の正しい array の値は、つまり (4) で計算した結果は { 1, 2, 4, 6, 9, 12, } になるはずです。しかし、(2) のコードでは、この値は、{ 1, 2, 4, 6, 8, 10, } になってしまい、(4) で計算した結果の値と異なった値を返すことになります。このように、現在の C 言語ではコンパイラによる解析やコード生成を妨げる要因が含まれています。
 今回の C 言語から利用できるようになる restrict 修飾子は、前述したようなポインタが同じ内容を指していないという情報をコンパイラに提供することができます。restrict はポインタだけを修飾するために利用することができます。(1) に restrict 修飾したコードを (5) に示します。


//(5)
void sumup(int n, int * restrict array1, int * restrict array2)
{
  assert((n % 4)== 0);
  for(int i = 0; i < n; i++) array1[i] += array2[i];
}

//(6)
  sumup(4, array+4, array);

//(7)
  sumup(4, array+2, array);

 (5) では計算中に array1 と array2 がけっして同じ内容を指していないという情報をコンパイラに与えています。したがって、コンパイラはこの情報を元に (2) のようなコードを生成することができます。このようなことから、(6) のような呼び出しは正しく動作しますが、(7) のような呼び出しをした場合には、言語的には未定義の動作になり、たぶん正しい結果を残さないでしょう。
 なお、ライブラリの中のポインタを引数に持つ複数の関数プロトタイプが restrict 修飾子付きの宣言に変更されました。

7.7 変数宣言と実行コードの位置関係
 変数を使うには、参照する前に宣言をする必要があります。このため従来の C 言語では、そのコードを含むブロックの先頭でまとめて行わなければなりませんでした。今回の新しい C 言語では、C++ 言語と同じように、ブロックの先頭という位置に縛られることなく、参照する前にどこでも宣言してよいということになりました。スコープは、その宣言された直後から開始されます。
 変数が初期化付きで宣言されている場合(例えば、int i = 3)、制御がその宣言部を通過する場合にのみ初期化されます。もし制御が通過しない場合その値は不定です。また前回そこを通過し初期化された後、再びループか何かで同じ宣言部にやった来たけれども、その宣言部分を飛び越えた場合でも、以前代入された値は忘れられており、不定値となります。
 また可変長配列を宣言した場合、その宣言を飛び越す前方ジャンプは禁止されています。なぜならば、飛び越されると、確保すべき必要メモリ量がわからないからです。
 さらに、for 文も拡張されました。for 文は、for( [1]; ...; ...) { ... } のように通常用いますが、なんと [1] の部分に宣言がおけるようになりました。つまり C++ 言語でのと同様に、for(int i = 0; i < 100; i++){ ... } のように記述することができるようになりました。なおこの i が有効なのはこのループの範囲内だけです。また使える変数は、auto 変数か register 変数だけで、static 変数などは使用できません。

7.8 指示付きの初期化子 (Designated Initializer)
 いよいよ、指示付きの初期化子 (Designated Initializer) の登場です。現在の C 言語では構造体や配列をあらかじめ初期化するには、始めの要素からすべて指定して初期化する方法しかありませんでした。その結果として、入力は非常に大変で、かつ見づらくなります。


#define ERRMES_NOERROR  0
#define ERRMES_FILEREAD  20
#define ERRMES_FILEWRITE 21
const char *errmessage[] = {
  "no error",
  NULL,
  ... 例えばここに 20 個空きがある ...
  "file read error",
  "file write error",
};

struct largestruct {
  int size;
  ... 例えばここにメンバが 10 個位ある ...
  int cx, cy;
} sls = {
  sizeof(struct largestruct),
  ... ここに明示的な初期化しなくてもいいメンバが 10 個位ある ...
  640,
  480,
};

 そこでこの新機能が登場です。この機能を使って書き直すと次のようになります。


#define ERRMES_NOERROR  0
#define ERRMES_FILEREAD  20
#define ERRMES_FILEWRITE 21
const char *errmessage[] = {
  [0]        = "no error",
  [ERRMES_FILEREAD] = "file read error",
  [ERRMES_FILEWRITE] = "file write error",
};

struct largestruct {
  int size;
  ... ここにメンバが 10 個位ある ...
  int cx, cy;
} sls = {
  sizeof(struct largestruct),
  .cx = 640,
  .cy = 480,
};


 非常にすっきり、見やすくなりました。このように、指示付きの初期化子は初期化時に配列の要素やメンバ名を指定することで、わかりやすく初期化を行えるようにするものです。そこでまず、配列の指示付きの初期化子を使う時には、要素を指定して初期化します。例えば、配列の 3 番目の要素を初期化したい時には、 [3] = ..., というように記述します。また構造体や共用体を初期化したい時には、.メンバ名 = ..., のように指定することで初期化を行えます。なお、static でない変数を初期化する場合には、初期値として定数ではない値、例えば他の変数を参照した式を指定する、といったことができます。

7.9 複合リテラル (Compound Literal)
 ついにこの機能の登場です。皆さんは次のような記述をする時に、これは面倒だと思ったことはないでしょうか?

typedef struct { int x, y; } point_t ;
void PutPixel(const point_t *p)
{
  ...
}

  ...
  point_t p;
  p.x = 640, p.y = 480;
  PutPixel(&p);
  ...

 関数 PutPixel の引数に単なる定数として point_t 型の値を渡したい。しかし現在の C 言語ではそのために変数を宣言してさらに値を代入する必要があります。しかし、この新機能を使えばこれは次のように書き直すことができます。

typedef struct { int x, y; } point_t ;
void PutPixel(const point_t *p)
{
  ...
}

  ...
  PutPixel(&(point_t){640, 480});
  //または
  PutPixel(&(point_t){.x=640, .y=480});
  //あるいは
  int pos_x, pos_y;
  ... 
  pos_x = 640, pos_y = 480;
  ...
  PutPixel(&(point_t){pos_x, pos_y});
  ...

 オブジェクト型または大きさの指定無しの配列を ( ) の中に記述して次に { } を記述することで複合リテラル (Compound Literal) を作ることができます。{ } の中は例にあるとおり指示付きの初期化子を使った初期化も可能です。また (int[ ]){1, 2, 3, 4} のような配列定数を作り直接関数引数として渡すことも可能です。ただし可変長配列の定数は複合リテラルで作ることはできません。

7.10 選択文と反復文のブロック化
 今度の C 言語では、複合リテラル (Compound Literal) の導入に伴い、選択文(if 文など)、反復文(for 文など)の扱いが変更になりました。具体的にはそれらの文は { } で囲まれているとして扱われることになりました。どういう意味でしょう? 例を示します。


//(1)複合リテラルを含んだ関数
int f(int par)
{
  int *a, *b;
  if( *(b = (int[]){par,1}) == 0)
   a = (int[])(par,2,4,8,16,32);
  else
   a = (int[])(1,3,5,7,9);
  //(*)ここで a と b が指しているのものは何か?
  return *a;
}

//(2) C99 での実際の扱い
int f(int par)
{
  int *a, *b;
  {
   if( *(b = (int[]){par,1}) == 0) {
    a = (int[])(par,2,4,8,16,32);
   } else {
    a = (int[])(1,3,5,7,9);
   }
  }
  //答え:a も b も指しているのものの保証はない
  return *a;
}

 (1)のように if 文で複合リテラルを使用して、ポインタ変数にそれを代入しています。さて、(*) で指しているものはいったいなんでしょう。実は、今度の C 言語では (2) のように、赤字で示されたブロックが暗黙的におぎなわれます。その結果として、a も b も指しているものの保証が無いことが保証されます。これは、将来の言語の使用を改定した時意図しない結果を巻き起こさないように導入したそうです。
 ただこれの導入により、既存のプログラムの意味が変わってしまう場合があります。(3) を見てください。


//(3) この変更が起こす問題
enum {a, b};
int g(void)
{
  if(sizeof(enum{b,a}) != sizeof(int))
   return a;
  return b; //b の返す値は何か?
}
//C89 の場合:g() == 0
//C99 の場合:g() == 1

(3) では始めに a==0, b==1 として enum 宣言をしています。関数 g の中では、sizeof のオペランドの中で b==0, a== 1 として enum 宣言をしています。したがって従来の C 言語では、enum{b,a} のスコープはずっと持続しているので、関数 g は 0 を返すことになります。しかし今度の C 言語では、if 文全体がブロックとして覆われています。したがって、enum{b,a} は if 文が終わった後は見ることができません。したがって、グローバルな enum{a,b} が見え、結果として関数 g は 1 を返してしまいます。

←前頁へ◎表紙へ次頁へ→
 
 
表紙 - 著作権 - 注意事項 - リンクについて - 404 エラーについて
(c)1999-2014 seclan. All rights reserved.