表紙 / 自作ソフト / 日記 / 宝箱 / サイト情報 / 検索
一般 / 新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 の書式

4. 配列
 今回の C 言語では、配列をより使いやすくするための機構が組み込まれました。その代表が可変長配列の機能でしょう。本章ではそれら追加機能について説明します。

4.1 可変長配列
 ある関数に入る毎に違った大きさが必要になる配列(可変長配列)を、今まではどのように作成していました? その作成方法は、大体次に挙げる方法のどれかでしょう。
 (1)あらかじめ最大の配列を作る方法。もし最大の大きさを見積もることができるのであれば、あらかじめその最大の大きさの配列を作ってそれを利用するという方法があります。しかしこの方法では最大が見積もれる時にしか使用することができず、また必ずしもメモリを有効に使用することができません。
 (2)malloc を使う方法。必要となる大きさを malloc で確保し、ポインタに割り当てて使うという方法があります。しかし、この方法では、一般的に malloc 関数のオーバヘッドが自動変数の確保より遅くなりがちであるので効率がよくないという問題がありました。また最後に領域を開放するのを忘れがちという問題もありました。
 (3)alloca を使う方法。プログラミング言語 C の標準関数ではないのですが、alloca という関数がよく使われていました。この方法では、この関数にいる間有効となるメモリ領域(通常スタック)のメモリを確保し、そのメモリを割り付けるという関数です。もちろんこの場合にはその関数を抜けると領域が自動開放されるので、領域の開放忘れはありません。しかし、alloca は演算子ではなく、単なるライブラリの関数なので、コンパイラが仮定するスタックの使い方に気をつけて使用する必要がありました。例えば、関数の引数の中で使用するとプログラムが正しく動かなくなる可能性がありました。

func(int sz)
{
  //(1)あらかじめ最大の配列を作る方法(ただし sz <= MAXSZ)
  int buffer[MAXSZ];

  //(2)malloc を使う方法
  int *buffer;
  buffer = malloc(sz*sizeof(int));

  free(buffer);

  //(3)void *alloca(size_t); を使う方法
  int *buffer = alloca(sz*sizeof(int));

  //(4)新しい C 言語の機能を使う方法
  int buffer[sz];
}

 以上のように可変長な配列を作成するにはいくつかの方法とそれぞれの問題がありましたが、今度の新しい C 言語の機能を使うとこの問題は解決します。
 (4)新しい C 言語の機能を使う方法。今度の C 言語では、配列の大きさの指定に数値定数だけではなく、数値変数も使用することができます。そのようなことから、今までの C 言語の表記と整合が取れたまま、かつ簡単に動的に可変長な配列を作成することができるようになります。
 さて、このような新しい機能は従来の例えば sizeof といった文法にどのような影響を及ぼすのでしょうか? ということで、これからその影響について説明します。

(1) sizeof の適用
 新しい C 言語で可変長配列が使用できることはわかりましたが、では C 言語の演算子 sizeof や、typedef を可変長配列に対して使用した場合にはどうなるでしょうか。まず始めに sizeof 演算子を適用するとどうなるかを示します。

sizeof 演算子の適用
size_t getN(int n)
{
  unsigned char buf[n];
  return sizeof(buf);
}
main()
{
  size_t sz;
  sz = getN(1);    //sz == 1
  sz = getN(256);   //sz == 256
  sz = getN(1024);  //sz == 1024
}

 getN 関数に引数 n を渡し、その大きさ分のバッファ buf を宣言します。そしてその大きさを sizeof で関数の戻り値として返します。main 関数では、引数に値を与え、戻り値を sz 変数に入れています。結果は、図からわかるように、引数と同じ値が sz に入ります。つまり、getN で sizeof している大きさは、静的に計算される値ではなく、実行時に動的に取られた値を返すことになります。

(2) typedef の適用
 可変長配列は、直接的な変数宣言だけではなく、typedef にも適用可能です。表記方法は通常の typedef と同じです。サンプルプログラムを以下に示します。


void test(int n)
{
  //(*)

  //int の要素数 n を持つ配列 arrayN_t の型定義 
  //実行時 n = 1 なら typedef int arrayN_t[1]; と同じ
  //実行時 n = 10 なら typedef int arrayN_t[10];
  typedef int arrayN_t[n];

  n++;  //n を一つ増やす。

  //arrayN_t 型の変数 A を宣言。
  //この時点での arrayN_t の配列要素数は typedef した時点(*)でのもの。
  //n++ した後の値ではない。
  //(*)の時点で n = 1 なら int A[1]; 
  //(*)の時点で n = 10 なら int A[10]; 
  arrayN_t A;

  //int 型の要素数 n を持つ配列変数 B を宣言。
  //n++ した後の値。したがって、
  //(*)の時点で n = 1 なら int B[2]; 
  //(*)の時点で n = 10 なら int B[11]; 
  int B[n];
}

 ポイントは、typedef された時点の値が typedef の型とされるということであり、typedef された型を使用した時ではないということです。
 ちなみに、n++ が宣言の後にあり、さらにそのあとで変数宣言が記述されているのを不思議に思った人もいることでしょう。今度の C 言語では、そのうち書きますが、C++ と同様にどこでも変数宣言をできるようになりました。したがってこのような記述も可能です。

(3) 関数引数での使用
 関数ブロックの中に、可変長配列を宣言することができますが、もちろん関数の引数にも使うことができます。例えば、(1)のようにして使用します。可変長配列の中に使う変数は、そこで使用する以前に他で宣言されていて、コンパイラに対して可視になっている必要があります。したがって、void sumup(int data[m][n], int m, int n){ ... } のような関数定義は、ファイルスコープレベルで m や n という整数変数が宣言されていない限りエラーになります。


//(1)関数本体の定義
void sumup(int m, int n, int data[m][n])
{
  int i, j, r = 0;
  for(i = 0; i < m; i++)
    for(j = 0; j < n; j++)
      r += data[i][j];
  return r;
}

 この関数に対するプロトタイプ宣言は、次の(2)のように記述することができます。特に、可変長配列であることを特に変数を示すことなく宣言できるようにするため、[*] という記述方式を使うことができます。この書き方はプロトタイプ宣言の中だけで使用することができ、関数定義では使用することができません。


//(2)プロトタイプ宣言
void sumup(int m, int n, int data[m][n]);
void sumup(int m, int n, int data[*][*]);
void sumup(int m, int n, int data[ ][*]);
void sumup(int m, int n, int data[ ][n]);

 しかし、(2)の例では、別に [*] を使わなくてもプロトタイプ宣言をすることができます。どうして、[*] という表記が必要なのでしょうか? そのための例を (3) に示します。

//(3)* がないと困る例
void sumup(data, n, m)
  int n, m;
  int data[m][n];
{
  ...
}
//(4)↑のプロトタイプ
void sumup(int data[m][n], int m, int n);//(4.1)m,n の宣言より前の参照!
void sumup(int data[*][*], int m, int n);//(4.2)OK

 この関数のプロトタイプの宣言を単純に記述すると、(4.1) のように記述することになりますが、この場合、m や n は data の中で参照する前で宣言されていないので問題になります。そのような時、(4.2) のように [*] という記述方法を使用することでそのエラーを回避することができるのです。

(4) 多次元配列へのポインタ
 可変長配列は、もちろんその型の配列を指し示すポインタにアドレスを代入することができます。しかし、従来の C 言語でそうだったとおり、配列の次元は一致している必要があり、さらに最上位次元の最大値を除いた他の最大値は一致している必要があります。例を示します。


int ar1[n][8][m];
int (*par1)[3][n-4];
int (*par2)[n][n+1];

par1 = ar1;  //(1)エラー: 8 != 3
par2 = ar1;  //(2)OK: ただし n == 8, m == n + 1 の時以外は未定義の挙動

 (1) の場合は二番目の配列の要素の最大値が ar1 は 8、par1 は 3 というように一致していません。その結果これはエラーになります。(2) の場合は par2 は n なので、自由な値を入れることができます。最後の要素も ar1 は m、par2 は n+1 なので同様です。しかしこれが実際に有効なのは各次元の最大値が一致している時です。つまり n == 8 でかつ m == n + 1 の場合以外は、未定義の挙動となるのです。

(5) 宣言可能個所
 可変長配列はどこでも使えるわけではありません。可変長配列が使えるのは、ブロックの中か関数引数/プロトタイプの中だけで、グローバル変数として宣言したり、struct や union の中のメンバとして宣言したりすることはできません。加えて、static や extern 付きの配列は、可変長配列にできません。


int x, y;
int newbuf[x][y];           //ERROR! グローバル変数

int sum(int w, int h, int dat[w][h]) //OK 関数引数
{
 static int sum_w[w];        //ERROR! static 付きの変数
 int sum_h[h];            //OK 通常変数
 struct sumdata {
  int sum_wh[w][h];         //ERROR! struct の中では宣言できない
 } sdat;
 static int (*p_sum_h)[h] = sum_h;  //OK static を使っているけどこれは OK
 ...
}

 
4.2 構造体中の 0 長配列メンバ
 プログラムを組んでいるとよくあるパターンの一つとして、可変長の配列を含んだ構造体が必要になることがありました。例えば以下のコードが挙げられます。

typedef struct {
  int attr;
  int num;
  int items[1];
} info_t;

...

info_t *info = malloc(sizeof(info_t) + sizeof(int) * (num-1));
info->num = num;

...

 この記述では、sizeof(info_t) は多くの場合、attr, num, items[1] の大きさの合計を返します。だから、例えば、items が一つもいらなかった場合に対応するために、sizeof で大きさをとる時には、その要素から num-1 といったように 1 を引く必要がありました。これは、書くのが面倒な上、さらに後からプログラムを見る時にもわかりにくい記述方法となっていました。今度の C 言語では、[ ] の中の大きさを、構造体の最後のメンバである時に限って省略することができるようになりました。例えば、次のように書くことができます。

typedef struct {
  int attr;
  int num;
  int items[ ];
} info_t;

...

info_t *info = malloc(sizeof(info_t) + sizeof(int) * num);
info->num = num;

...

 この書き方を用いれば、sizeof が返す値が、attr と num の合計になります。しだがって、ただ単純に num をかけるだけで必要な要素のメモリを確保することができます。その結果として、見た目にもすっきりした形態で記述できます。

4.3 配列要素中の記憶/型修飾子
 配列を宣言する時には、今までは [ ] の中には整数定数を書くことしかできませんでしたが、今度の C 言語では、可変長配列を記述することができるようになったことは以前示しました。実は今度の C 言語ではさらに [ ] の中に static, restrict, const, volatile といった記憶修飾子、型修飾子を記述することができます。修飾子とその効果は次の通りです。

修飾子とその効果
修飾子サンプル説明
staticf(int p1[static 10]);少なくとも 10 個はアクセスでき、p1 は NULL ではないことを保証
restrictf(int p1[restrict], int p2[restrict ]);p1 と p2 は同じ配列でないことを保証
constf(int p1[const]);p1 は変更できない(p1の内容は変更できる)。f(int * const p1); と同じ。普通に配列を宣言するのと同じ。
volatilef(int p1[volatile]);p1 が予期せず変更される可能性がある。f(int * volatile p1); と同じ。


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