C言語の最新事情を知る(3)
C11の仕様-それ以外の主な機能強化点
C言語の最新仕様の情報にキャッチアップしよう。C11の仕様で強化された機能のうち、脆弱性対応以外の主要な強化点を紹介。
前回は、C11の仕様(ISO/IEC 9899:2011)の中から主に脆弱性対応に関連するものを紹介した。今回は、それ以外の主な強化点を紹介しよう。
なお、C11の仕様書はANSIのeStandards storeからも購入できる。また、commitee draftであれば、www.open-std.orgで閲覧可能だ。現在はこのリンクからダウンロード可能(ただし、詳細は確定した仕様とは異なる場合があるので注意されたい)。前回同様、各項目には仕様書の章、節番号を付記しているので、詳細に興味を持たれた方は仕様書を参照してほしい。
無名構造体/union(§6.7.2.1)
C11では無名構造体/union(共用体)を使用できる(リスト1)。
typedef enum {
INT,
FLOAT,
} DataType;
typedef struct {
DataType dataType;
union { // (1)
int integer;
double floating;
}; // (2)
} Data;
int main(int argc, char ** argv) {
Data data = {
.dataType = INT,
.integer = 123, // (3)
};
// ……
}
|
「無名」という言葉は、構造体/unionの識別子(いわゆるタグ名)が省略されていること(リスト1の(1))、メンバー名が省略されていること(リスト1の(2))の両方を指している。メモリ上での配置は通常の構造体/unionと変わらない。つまり、リスト1のintegerメンバーとfloatingメンバーは同一のアドレスに配置される。無名union/構造体内のメンバーへは、その無名union/構造体をメンバーとして持つunion/構造体のメンバーと同じようにアクセスできる。(3)を見ると分かるとおり、dataTypeメンバーと同じようにして、integerメンバーにアクセスできていることが分かる。
総称選択(§6.5.1.1)
総称選択(Generic selection)は、型に応じて分岐する式である(リスト2)。
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
float: cbrtf, \
default: cbrt \
)(X)
|
cbrt関数は立方根を計算する関数だが、引数の型に応じてlong double用のcbrtl関数、float用のcbrtf関数、double用のcbrt関数の3つのバリエーションが用意されている。このため、これまでは引数の型に応じて適切な関数を使い分ける必要があった。リスト2は総称選択により型に応じて適切な関数が選択されるように書かれたマクロで、これを「総称型マクロ(type-generic macro)」と呼ぶ。総称選択の文法をリスト3に示す。
generic-selection:
_Generic ( assignment-expression , generic-assoc-list )
generic-assoc-list:
generic-association
generic-assoc-list , generic-association
generic-association:
type-name : assignment-expression
default : assignment-expression
|
_Genericの最初の引数には、制御式(controlling expression)としてassignment-expression(6.5.16)を指定する(assignment-expressionは名前からは代入式しか指定できないように思われるが、§A.2.1を見ると分かる通り、定数や、文字列リテラル、関数名や変数名などの識別子を含む様々な式が記述可能)。そして、この式の型により総称選択の動作が決定される。第2引数以降に総称関連(generic association)のリストをカンマ区切りで指定する。総称関連は「型名:式」の形式か「default:式」の形式で、これらのリストの中から制御式の型と互換性のあるものが選択される。制御式の型と互換性のある型を総称関連のリストに最大1つ指定する必要がある。defaultは最大1つ指定可能で、他の総称関連が制御式の型と互換性が無い場合にはdefaultに指定した式が採用される。defaultが無い場合は、制御式の型と互換性のある型が総称関連に1つ指定されている必要がある。
制御式は、総称選択で評価されるわけではない。総称選択はあくまで型を基に、総称関連リストの中から適切なものを選択するだけである。例えば、リスト2で定義したcbrt(x)マクロを使った呼び出しはプリプロセッサによってリスト4のように展開される。この場合は制御式に渡される「++i」がint型なので、cbrtが選択され、最終的にcbrt(++i)呼び出しが行われる。一見すると++iが2回評価されてcbrt(2)が計算されそうだが、制御式は評価されないので、cbrt(1)が実行される(なお、手許のclang-500.2.79では、multiple unsequenced modifications警告が表示された)。
// 展開前のコード
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
float: cbrtf, \
default: cbrt \
)(X)
int main() {
int i = 0;
printf("%f\n", cbrt(++i));
}
// 総称選択部分の展開後のコード
printf("%f\n", _Generic((++i), long double: cbrtl, float: cbrtf, default: cbrt )(++i));
実行結果:
1.000000
|
関数指示子(§6.7.4)
inline関数指示子
インライン関数指示子(inline)を伴って宣言された関数はインライン関数である。インライン関数は、その関数の呼び出しをできる限り速く行えることを要請する(これは例えば通常の関数呼び出しの仕組みを用いる代わりにインライン化することで実現する)。
#include <stdio.h>
inline int max(int i1, int i2) {
return i1 >= i2 ? i1 : i2;
}
#define MAX(X, Y) ((X) >= (Y) ? (X) : (Y))
int main(int argc, char **argv) {
int i = 4;
printf("%d\n", max(++i, 5));
i = 4;
printf("%d\n", MAX(++i, 5));
return 0;
}
結果:
5
6
|
リスト5は、最大値を求める処理をインライン関数と従来どおりにマクロを用いて書いた例だ。マクロの場合は引数が2回評価されるので、2回インクリメントされて結果は6になるが、インライン関数では5となる。
inline関数指示子を関数に付与することで、関数の呼び出しがなるべく速く行えるようにすることを処理系に対して要請できる。ただし処理系が実際にどのようにコンパイルするかは実装依存である(実装によってはインライン化を全く行わないかもしれない)。
関数のインライン定義は、関数の外部定義を提供しない(つまりstatic宣言された関数と同様に、他のコンパイル単位からは見えない)し、他のコンパイル単位で、同一名の関数を外部定義することを禁止しない。つまり同一名のインライン定義と外部定義は同居できる。同一コンパイル単位内に同居することさえできる。図1に示すinline01.cファイル、inline02.cファイル、inline03.cファイルは同時にコンパイル、リンクできる。
_Noreturn関数指示子
_Noreturn関数指示子を伴って宣言された関数は呼び出し元に戻らない。もしも_Noreturn宣言された関数の制御パスの中に呼び出し元に戻るパスがあるなら、コンパイラは警告を表示できる。
_Noreturn void foo(int i) {
if (i > 0) abort(); // i<= 0の場合の挙動は未定義
}
|
まとめ
前回と今回の2回に分けて、C11の仕様について主だったところを紹介した。
- 構造体/unionでタグ名とメンバー名の両方が省略可能となった(無名構造体/union)
- 型によって式を選択する、総称選択が追加された
- inlineと_Noreturnの2つの関数指示子が追加され、インライン関数、制御の戻らない関数を宣言、定義できるようになった
今回は紹介できなかったが、C11ではマルチスレッドの制御のためのライブラリ群が追加された。これによりマルチスレッドプログラムの移植性向上が期待される。興味のある方は仕様書をひもといてみてほしい。
今回紹介した機能のいくつかは、まだ処理系にきちんと実装されていないものもあり、執筆に当たっては実際の処理系での動作を確認できなかったものがある。仕様を注意深く読んで間違えの無いように配慮したつもりだが、もしも筆者の理解に間違いがあるようであれば、ご指摘いただけるとありがたい。