C言語機能の比較

2013/06/16 1st
2013/07/3 テスト追加修正

windowsで動くCコンパイラの機能(kr,c90,c99,c11,vc・gcc拡張)の実装具合を比較してみた。
試してみたソースは

こちら。 (zip)

環境は win8(x64) または win2k(x86) で、コンパイラと結果は以下(並べると多かったので表は分割)

  • VC以外はフリー配布のコンパイラを使用(vcも無料なexpress版あり)。
    フリーかつwindows版で今配布されているもので、そのコンパイラの最新とは限らない。
    (vc,gcc,clang以外はあまり現役コンパイラとして実使用されてなさそう)

  • Cコンパイラオプションについては、[c基本機能][old-c特有][c++準拠] は(ほぼ)デフォルト、[c99機能][c11機能]については 必要に応じて c99、c11 を有効にするオプションを指定、[日本語文字関係] は必要に応じて SJISやUTF8 を有効にするオプションを指定、している。

  • C++ コンパイラとして試すときは c++機能をなるべくonにする設定にしている。また c99,c11 については、可能ならば c++11 を有効にするオプションを指定している。

  • 各コンパイラ固有の補足は 結果1, 結果3 のほうに記述.

  • 結果は、パスすべきテストの場合は、pass(パスした), fail[e](実行失敗), fail[c](コンパイル失敗)。
    出来なくてもおかしくない類は enable(可能), disable[e](実行できなかった), disable[c](コンパイルできなかった) あるいは no support(サポートしていない機能)
    サイズ関係は サイズ表示またはno support、その他項目に合わせた文字列、のようにしている。

試した内容について

規格をちゃんと把握しているわけでなく、本やネットでなんとなく分かる範囲で、ざっくり c90 や c99, c11 のように分けている。(ので間違ってたらすみません)

元々 C++機能の確認のつもりで行なっていたので、cとc++ で非互換のある機能については表では別枠にしている。(そういう意味では結果2が主目的。 C++ の機能を見ようとして C言語部分だけで力尽きた状態)

c++ と矛盾しない範囲の機能について

[c基本機能]

old-c(kr-c)のころから変わらない機能と思われるもの。old-cを使っていた時代に見聞きしたコンパイラによってはサポートされてない機能の確認を用意してみた。 実際には、現環境で動く old c の持ち合わせがないのであまり意味がなかったが。 でも そういうつもりだったので引数なし関数は f(void) でなく f() のように極力 void の使用をさけている。

その中で少しびっくりしたのは int 値とunsigned 値の比較で bcc 5.5.1 が違う結果になってしまったことだろうか。intより幅広の整数への暗黙の昇格が発生することがあるか?とうろ覚えゆえ試したのだが……アセンブラソースをみるとそんなコトではなく setl al となっていて単純に生成ミスのようだ。(unsigned)n < 10U のように記述すれば setb al が生成されるので 10U の Uの情報がどこかで欠落したようにみえる。実際のプログラムでは型警告回避で(unsigned)にキャストしていることは多そうだし、そもそもunsigned化のテクニックを嫌がる人は符号付きで 0 <= n && n < 10 チェックしてるだろうで、バグに気づく前に回避されてることが多そうには思う。(がこのレベルのバグに会うとコンパイラへの信頼度はぐっと下がるだろう)

int より幅広(long まはたlong long)でサフィックス(LやULL等)のつかない整数値リテラルについては、bcc 5.5.1 では long long 型無くサフィックス ll, ull も使えず、しかし__int64 型有りで ll,ull無しでも 64bitの10進数16進整数リテラルが使えていた、ため試してみたのだが、他のコンパイラでもわりと――少なくとも16進数表記であれば結構サポートされているようだ。実装を思えば16進対応は(コンパイラ)実行時のコスト少ないので納得するが、10進16進で違う選択をするとは思っていなかったので少しびっくりだった。

[old-c]

8進数の8,9はさすがに歴史的で、pcc のみが使えてしまった模様。関数内のextern 変数が外部に見えてるは dmc と orange-c(cc386) で古くからのコンパイラのようなのでその名残なのだろうか。

[c90で確定した機能]

おもに「プログラミング言語C 第二版」付録C変更点の要約、をみながらテストを用意。 これらも概ね、パスする状態。

違いがでているのをみると、

  • sizeof(L'C') の結果は、windows対応が進んだコンパイラだとutf16対応で 2 になっているが、unixから移植されただけの状態のコンパイラだと 4 になってる。
  • sizeof の結果の型が unsigned(uintptr_t)でなく signed(intptr_t)にならないコンパイラがいくつかあった。ansi-c以前の標準器の pcc はまさにそういうものなのだろうか (型に敏感なC++はともかく Cでは影響は少なそうに思う)
  • 16ビット向のlsic86 と 実験実装的なcoinsで L'C'関係が未対応なのは ワイド文字の必要性が少なそうなのでそういうものだとも思う(けど、同じ作者が関わっているのでひょっとすると意図的な選択かもしれない?)
  • Tiny Cで 構造体返す関数がコンパイルできていないが、これは x64版の不具合のようで、表にはしてないが x86 版では pass している。
  • Orange C のみ const volatile 変数テストが実行時エラーになってるが、そもそも ローカル変数で int const volatile a = 0; 定義するのがよくないような気もする。

[c99機能]

必要ならば C99 を使うためのオプションを指定してコンパイル.

おもに「プログラミング言語 C の新機能」のサイトの頁をみてテストを作成。(ドラフトをベースにしてるようなことを書かれていて、実際のコンパイラのサポート具合をみると、多少ドラフトから変わったのかなと思えることもある)

c99以降はコンパイラの対応が結構ばらばらになる。C++ 主体の MSVC(やbolrand系)は c99の対応を見合わせているようだし、c99 対応を謳う中でも対応具合に幅あるし、で。
(vcは c++ 対応と矛盾しない機能については思ったより対応している感じ)

そうはいってもさすがに //コメント くらいはよほど古いコンパイラ以外はサポートしてそうだ(1993年製のlsic86v330はさすがに未サポート)。 enum の余分な,も簡単な対応だろうでみな大丈夫だし。 負値の絡んだ%余算の扱いも、大昔の8,16bit CPU によっては違うことがあったが、最近の CPU はネイティブでそうなってると思う。 long long や可変引数マクロもここ10年くらいのコンパイラならそこそこサポートしてるようだ。

ただ構造体メンバーでの 空配列定義 が意外にばらついている印象。

プリプロセッサ関係では、2引数以上のマクロでの空引数対応はc99以前のコンパイラ以外はわりと対応しているようだが、1引数マクロでの空引数の扱いはまちまち。これはどちらがいいとも言い難いが1引数マクロの引数指定忘れを検出できるほうが個人的にはありがたい。(c99/macro_empty_arg2.c のように1引数マクロの中でダミー使って回避できそうなので)

行コメント//末の \ の扱いが仕様通りでないコンパイラもあるが、これは通常の書き方では避けるべきものとして普通は回避されていることが多そうだろう。(日本の場合 SJIS で 'ソ'や'表'等の '\c5c' を含む文字が行末に来た場合の問題もあるのでさらに面倒あり)

あと C++(C++11含)と非互換でない機能で VC(11) でサポートしていないのは、 特定の可変引数マクロc99/macro_va_2.c(これはvcバグ仕様?)、__func__(__FUNCTION__はある)、_Pragma、inline関数(__inlineなら可)、ワイド文字列リテラルとそうでない文字列リテラルの連結、16進数浮動小数点リテラル、(型名){...} による型値リテラル、c++のような文後の変数定義、c++互換のforの第一引数の変数宣言。

[c11機能]

必要ならば C11 を使うためのオプションを指定してコンパイル. (C11がなくC99があればC99を設定)

c11 については詳しいまとまった日本語サイトはなさそうで、個々の検索でひっかかったものや、ドラフトのpdf を参考にした。(……あまりよくわかってません)

c99に対して言語機能の追加として _Static_assert, _Noreturn, _Alignas, _Alignof,_Generic, Unicode文字列関係等があり、オプショナルな追加として thread関係(_Thread_local)、atomic関係 等がある。またc99で必須だった 可変長ローカル配列変数、complex型、関係がオプショナルになっている……が、このへんは(ヘッダを経由しない場合) 大半 c++とは非互換の状態。

c++と互換があるのは、unicode文字列関係や、もともとc++の機能だった 無名構造体・無名共有体・同typedefの複数回定義 あたりだろうか。

コンパイラ側の実装は、gcc 4.8 や clang 3.3 では、(ほぼ)実装されている模様。msのほうも vs2013 でc11(c99)を部分的にもサポートするらしい(2013preview ではまだ何もだけど 2013rtmあたりかららしく)。

[日本語文字関係]

ソース中に シフトJIS文字や UTF-8 文字を使った場合の具合の確認。L'C'等の対応とは別次元の対応なので分けて試した。

マルチバイト文字を扱うのにオプションが必要ならばそれを設定した状態でコンパイルしてる。

対応具合は結構まちまち。

SJIS を使う場合、\=0x5c を全角2バイト目に持つ文字があるため、未対応のコンパイラだと意図せず\エスケープ文字に化けて不具合になる場合があるのでそれらの確認。環境によっては utf8 への移行が進んでるので機会は減っているが、Windows は標準が SJIS (DBC)のため 0x5c を含む文字に注意する必要がある。

gccの場合このへんのオプションが配布バイナリによって機能したりしかかったりなので注意がいる(SJIS指定はtdm版は使えるがmingw本家版は無視される)

VCは ソースが SJIS で書かれていようと UTF8,UTF16 で書かれていようと'C',"C" は SJIS(というかmbc)、L'C',L"C" は UTF16 でコンパイルされるので、 utf8文字列のバイナリをつくるのが困難(直接ソース中にutf8文字列を記述しない方法で作るしか。u8"文字列" が使えるようになれば状況改善せずに回避策が取れることになるが、素直にutf8を扱えるモードはやっぱりほしいところ)

gccや clang が変数名関数名等で unicode文字が使えないのは意図的なのだろうが(あるいは配布Cコンパイラのバイナリ作成時のオプションの都合?)、個人的には出来てほしい。\u???表現まで可能なのはやり過ぎに思う面もあるがリンカがutf対応していて他言語をC/C++に変換する用途等では出来たほうが楽なこともありそうか。

[c++準拠]

vc では古くから c++機能の一部が c言語側で利用できてる状態で dos/windows 系コンパイラの場合は追従してサポートしていることが多そう(vc拡張といえるけど一応c++の機能なので別枠扱)。
といっても、無名構造体・無名共用体 や 同typedefの再定義 は c11 に入ったのでここは void main で暗黙に 0を返す件のみ。

mingw / clang では cでコンパイルすると不定値を返す状態で、c++でコンパイルすると型エラーでコンパイルが通らない状態。今回の表には含めていないけれど cygwinやlinux環境の mingw/clang でも概ねそのよう...

正直 c++ だと void main は ok と言う場合は、でもgccとか未対応あるよ、ってのも言わないと不味いような気はする。

追記: void main がc++でokというのは間違いのよう。int main 関数につき、return が抜けてる場合に return 0; を補なわれる、ということのよう... なので void main が許されるのは vc拡張の類( 表とか やり直さないとだけど ... 後日)

[msvc拡張] [gcc拡張]

gcc,vcとも言語拡張はたくさんあるが、そのうちのわりとメジャー?そうなもの(で主に#defineマクロで違いを吸収できそうなもの)を適当に確認。 いくつかの機能はc99,c11で標準化済みだが、まだc99,c11サポートが不十分なものもあるので、チェックしている。

インライン展開されることを期待して、ライブラリ関数で組込関数になっているものもも一部テストしているが、 生成コードを確認していないのでコンパイラによっては関数呼び出しになっているものもあるかもしれない。

テストした範囲で vc,gcc,clang 共通で使えているのは __m128 (要SSE), #pragma once, __COUNTER__, __restrict (c99のrestrict), __FUNCTION__ (c99の__func__) あたり。

mingw が __declspec をサポートしているようなのでこの表では共通化されているように見えてしまうが、mingw でない本来の gcc では __declspec をサポートしていないので、そのへんはさっぴいている。

__m128はgccでは SSE を有効にしないと使えないが、SSE絡みの組込関数は多く、使える場合わりとvc,gcc間で共通化されているよう(未チェック)

__restrict はc++でも使え vc,mingw以外でもあるようで restrict / __restrict 機能自体は結構普及しているのかもしれない。

 #pragma onceはwin系コンパイラでは古くからあり、今では(この表にないコンパイラ含め)他でもぽつぽつサポートされている印象。

__COUNTER__ は vc,gcc,clang 以外ではほとんどサポートされていない。(が #pragma once とちがいユーザー側で対処しにくい機能なので普及してくれるとありがたいのだが)

c99,c11 で標準化されている機能と同種のものとしては、 _declspec(noreturn) / __attribute__( (noreturn) ), __declspec(align(N)) / __attribute__( (aligned(N)) ), __declspec(thread) / __thread , _InterlockedIncrement / __sync_add_and_fetch , _InterlockedDecrement / __sync_sub_and_fetch , _InterLockedExchange / __sync_val_compare_and_swap , あたりか。 (atomic関係はライブラリ関数でネイティブになるとは限らないが)。

また標準化されていない vc,gcc(clang) にある同種の機能としては、 __FUNCSIG__ / __PRETTY_FUNCTION__ , __force_inline / __attribute__( (always_inline, ) ) , __declspec(noinline) / __attribute__( (noinline) ) , __declspec(selectany) / __attribute__( (weak) ) , あたり。 このテストではvc,gcc,clang以外でのサポートはいまいちだが、x86以外のcpu向けの現役のc/c++コンパイラだとgcc(vc)互換機能はわりとありそうにも思う(が未確認)。

windows 専用としては __declspec(dllimport)、__declspec(dllexport) はほぼ必須機能かもしれない。(これがないとwindows-apiライブラリを用意するのが面倒だろうで)。 __cdecl, __stdcall あたりも win系はなんらかの形で対応してるだろう。__fastcall もそうだが前2つよりかはサポートされていないこともあるようだ。またサポートしていてもレジスタの扱いはコンパイラによって違う模様。

なお __attribute__ は前置での使用のみテストしている。
__attribute__ は当初後置記述のみだったのが後から前置可能になったこともあり、少し古めのgcc(互換)コンパイラでの機能を確認するなら後置も試したほうがよいだろうが、面倒なのでそこまでしていない。(#defineマクロで違いを吸収しにくいということもあり)

その他 gccの2進数リテラルは c++14で採用?されるらしいので、試してみた。 (gcc v2.9xの頃は整数値リテラルの途中に_をかけたような気はするけれど今は駄目なのね..でそのテストは破棄)

関数引数や戻り値の性質チェック関係でも vc,gcc で同様の機能はあるようだが、面倒なので未確認。

※ c99,c11で標準化されていても実装にばらつきがあると結局 vc拡張 gcc拡張と同様の印象をもったため、vc,gcc拡張も試してみたのだが、わりと量が多くなったので、標準機能とは別で試したほうがよかったかもしれない。

c++ と矛盾する C機能について

c/c++は些細とはいえ結構細かい違いが多々出来てしまっていたのだなあ、と改めて思う。

といっても本来標準ヘッダを include して c++ と同名の名前を用いて使うのが筋だと思れる _Bool, _Static_assert, _Noreturn, Alignas, _Alignof, _Atomic, _Thread_local あたりの生の名前を試していたり、そもそも互換機能確認用に用意されたマクロの有無を 違いとして並べているせいもあるが。

c++非互換のc機能はエラーになっても当然だが、どのコンパイラもいくつか使えてしまう機能が残っている。そういう機能は使われると移植でたちが悪くなるが、こういう一覧で見る分にはちょっと楽しいかもしれない。

特に Borland C++ 5.5.1 時点では枝葉の機能が 結構 Cコンパイラのままだったのは興味深い。class テンプレートのメンバー関数テンプレート等わりと大きめの機能はちゃっちゃと入っていたりするので、無いと代用不可能な機能を先にいれて枝葉は後回しにした、といった感じなのだろうか。

[C基本] [old-c] [c90] 関係

  • sizeof('C') に関しては C++ では 1で当然だが、dmcやlsic86 では Cでも 1 になっている。これは 多少 C非互換になっても 8/16bitCPU向のコードとしてよくなるような選択をしていた類だったように思う。

  • &配列名 でのアドレス取得が C++ ではエラーになるのは、これを試すまでは意識していなかった。というか結果をみて C++ は未対応なんだと判断。

[c99機能] [c11機能] [gcc拡張]

  • restrict は c++ では使えないが 代わりに vc/gcc拡張で __restrict があるので機能自体はそれなりに普及しているかもしれない。

  • 実行時サイズ指定のローカル配列変数は、c99では標準装備だが c11 では オプション扱いも可能なようで、その場合 __STDC_NO_VLA__ がマクロ定義されている。

    また こちら みてると c++14 で入るらしい。ただし sizeof や typedef するのは c++14でも未対応.

    正直 VLAの sizeof,typedef の扱いや関数引数でのサイズ後置指定(旧式関数宣言必須)をみてると、頭沸いてる 考えすぎなのでは思ってしまう。C++のこと抜きにしてもC99採用しなかったMSのほうが正常に思えてくる(いや stdint.h や long long 等 c++9x標準化待たずにやってほしかった類は多かったのだが)(追記:VLA関係はほぼgccのをもってきただけのよう?で少し言い過ぎたので微修正)

  • 複素数型は、c99の時点でコンパイラ間の都合のような折衷案にみえる。
    • _Complex は共通しているが、_Imaginary は __STD_IEC_559_COMPLEX__ が定義されている場合のみ存在する。gcc (およびclang) では _Imaginary をサポートしないようなので、余計に使いづらい状態に思われる。
    • gccと一部互換コンパイラは 1.0+2.0i のような式表現が可能になっている。c11からcomplex.h ヘッダにマクロが追加されて 1.0+2.0*I のようにかけるようになったらしいが…cで複素数計算を必要としてる人はとうにgcc拡張が普及しているような気はする。
    • 複素数型も c99 では標準装備だったのが c11 ではオプション扱い可能になった。その場合は __STDC_NO_COMPLEX__ がマクロ定義される模様。


2013-06-17追加. こちら の件のテストを追加。20130616のでは g++ で c99 c11 を試すのに -std=c++11にし忘れていたのを修正.その他諸々.
2013-07-03 テスト追加(dup_typedef, __builtin_bswap16/3264, sizeof_int128, switch_i128). vc組込関数テストの調整. その他諸々