C11では、C++(C++11以降)にはあるいくつかの型がサポートされません。逆に、C11にはあってC++にはない型もあります。また、型変換についてはかなり仕様が異なりますので、今回はそのあたりの解説を行います。
C11にはない型
構造体と共用体以外のクラスがないのは当然として、それ以外にも、C++にはあるけれどもC言語にはない型がいくつかあります。
bool型
C11にはbool型が存在しません。ただし、論理型として_Bool型があります。また、<stdbool.h> では、bool, true, falseという名のマクロが、それぞれ、_Bool, 1, 0 に定義されます。しかし、旧規格との互換性のため、論理値をあらわす必要があるときには、普通はint型を使います。したがって、等価演算子(== および !=)、関係演算子(<, <=, >, および >=)、論理演算子(!, &&, および ||)の評価結果は、C++ではbool型でしたが、C11ではint型になります。
wchar_t型
C11には、基本型としてのwchar_t型がありません。しかし、ワイド文字もワイド文字列も扱うことができます。では、どうしているかというと、wchar_t型は基本型ではありませんが、typedef名として標準ライブラリで定義されています。wchar_t 型を定義しているヘッダは、<stddef.h>, <stdlib.h>, <wchar.h>, <wctype.h> です。
char16_t型、char32_t型、char8_t型
C++11から導入されたchar16_t型とchar32_t型は、C11ではwchar_t型と同様に単なるtypedef名です。これらの型定義は<uchar.h>で行われています。また、C++20から導入されたchar8_t型はC11にはありません。文字列リテラルのプレフィックスであるuやUもC++と同様に使うことができます。u8も使うことができますが、C++17までと同じで単なるchar文字列になります。
参照型
C++には、オブジェクトに別名を付けるための純粋な参照型がありましたが、C11には参照型はありません。参照が必要な場合は、ポインタを使うことになります。
メンバへのポインタ型
C11には、構造体や共用体のメンバへのポインタ型というものがありません。それにともない、.*演算子や->*演算子も存在しません。メンバへのポインタと同等のことを行うには、構造体や共用体へのポインタと指定するメンバのオフセット値を併用する必要があります。メンバのオフセット値はoffsetofマクロで取得することができます。
C++になく、C11にはある型
C言語の旧規格やC++にはないけれども、C11にはある型がいくつか存在します。
複素数型
C++では、複素数はstd::complexクラステンプレートを用いて表現しますが、C11には複素数を表現するための組み込み型があります。複素数型を表すには_Complexというキーワードを用います。そして、float _Complex, double _Complex, long double _Complexのように精度を指定します。
<complex.h>では、_Complexに展開されるマクロcomplexが定義されています。また、虚数単位を表すマクロIも定義されています。そして、複素数型の定数は、1 + 2 * Iのように表現することができます。
処理系によっては、純虚数を表現するために_Imaginaryというキーワードを使えることがあります。そして、float _Imaginary, double _Imaginary, long double _Imaginaryのように精度を指定します。_Imaginaryを使える場合、<complex.h>では_Imaginaryに展開されるマクロ imaginary が定義されます。_Imaginaryは任意実装ですので、常にどんな処理系でも使えるわけではありません。
C99ではフリースタンディング環境のみ複素数型が任意実装でしたが、C11ではホスト環境でも任意実装になっています。
可変長配列
C11では、実行時に要素数を決定することができる可変長配列がサポートされる場合があります。
ただし、静的記憶域期間を持つ可変長配列を宣言することはできません。可変長配列は、次のようにして使います。
0 1 2 3 |
int n = 10; int array[n]; |
可変長配列が存在することで、C11におけるsizeofの式は、必ずしも定数式になるとは限りません。
C99とは異なり、C11では可変長配列は任意実装なので、すべての処理系がサポートしているとは限りません。
アトミック型
C++11以降にはライブラリ機能として提供されるstd::atomicクラステンプレートがあります。C11にはクラステンプレートはありませんが、代わりに_Atomic修飾子があります。_Atomic修飾子は次のように使います。
0 1 2 3 4 |
_Atomic int x; _Atomic(int) y; atomic_int z; |
上記の3つは同じ意味になります。また、最後の形式を使うには<stdatomic.h>をインクルードする必要があります(何らかの操作を行う場合も<stdatomic.h>で定義された関数やマクロが必要になります)。
アトミック型も任意実装ですので、すべての処理系で使えるとは限りません。
暗黙的な型変換
C++にくらべて、C11はかなり豪快に暗黙的な型変換が行われます。具体的には、オブジェクト型または不完全型へのポインタは、任意のオブジェクト型または不完全型へのポインタに暗黙的に型変換することができます。例えば、char* から int* とか、void*からdouble*とかに自由に型変換できるわけです。もし間違った代入などが行われていても、コンパイラは警告を出すことはあってもエラーにすることはありません。
0 1 2 3 4 5 6 7 8 |
int main(void) { char *s = "abc"; int *p1 = s; /* OK */ double *p2 = malloc(sizeof(double)*10); /* OK */ return 0; } |
さらには、ポインタ型と汎整数型のあいだでも暗黙的な型変換が可能です。
0 1 2 3 4 5 6 7 8 |
int main(void) { char *s = "abc"; int a = s; /* OK */ int *p = 1234; /* OK */ return 0; } |
さらに、関数へのポインタは、任意の関数へのポインタ型に暗黙的な型変換を行うことができます。仮引数の型や個数が異なっていても、返却型がまったく異なっていても、何のチェックもされません。
このように、C++ではコンパイルエラーになるような暗黙の型変換ができてしまいます。多くの場合、こうした暗黙の型変換は間違いですが、コンパイラはエラーにしてくれません。プログラマは自分の責任において、こうした間違いを防がなければなりません。
明示的な型変換
C11には、const_cast, dynamic_cast, reinterpret_cast, およびstatic_cast演算子がありません。使えるキャスト演算子は、(型名) だけです。int(a)のような関数形式のキャストも使うことができません。