プログラミング
あなたのC言語は標準C言語ではない
your C is not standard C (sebsite.pw)
要約
この記事は、GCCやClangといった主要なC言語コンパイラが、標準C言語の仕様から逸脱しているいくつかの例を挙げています。asmキーワードの拡張、FLT_ROUNDSマクロの静的な定義、標準プラグマの未サポート、関数戻り値の型修飾子の扱い、そしてmusl libcにおけるoutdatedなva_start定義など、開発者が直面する可能性のある非互換性について解説しています。
全文翻訳
あなたのC言語は標準C言語ではない
2026-10-12
委員会は、誰も実際には使用できない、あるいは使用しないであろう非現実的な言語を作り出した。 - デニス・リッチー(文脈を離れて)
これは、標準で書かれているC言語と、gcc/clang + glibc/muslを使用する人々が実際に使用しているC言語との間の、いくつかの非互換性の不完全なリストです。
asm
デフォルトでは、gccとclangはasmキーワードを拡張として認識します。これは厳密には準拠していません。なぜなら、標準C言語では、asmは有効な予約されていない識別子だからです。そのため、標準に準拠したコードは、デフォルト設定のgcc/clangではコンパイルできない可能性があります。そして、それは構わないのですが、問題は2つあります。1つは、コンパイラがデフォルトで準拠していないという事実、そして2つ目は、なぜ標準でasmを予約語にしなかったのかということです。C++はそうしています。C言語がそうしない理由はよくわかりません。
FLT_ROUNDS
標準はfloat.hでFLT_ROUNDSマクロを次のように指定しています。
5.3.5.3.3p23: 標準浮動小数点型に対する浮動小数点加算の丸めモードは、FLT_ROUNDSの実装定義値によって特徴付けられます。FLT_ROUNDSの評価は、<fenv.h>の関数fesetroundによる実行時丸めモードの変更を正しく反映します。
注目すべきは、標準ではこの値が実行時の浮動小数点環境とともに動的に変化することが要求されていることです。しかし、gccは実行時の丸めモードに関係なく、常にFLT_ROUNDSを1(最近接丸め)に定義します。
/* 加算は0:ゼロ、1:最近接、2:+inf、3:-inf、-1:不明に丸められます。*/
/* ??? これは<fenv.h>のfesetround呼び出しで変化するはずです。 */
#undef FLT_ROUNDS
#define FLT_ROUNDS 1
clangはこのための組み込み関数(__builtin_flt_rounds())を定義していますが、gccはなぜか定義していません。
標準プラグマ
将来のある時点で、浮動小数点演算の動作を制御するための5つの標準プラグマをすべて専用のブログ記事にするつもりですが、非常に非常に短いバージョンは次のとおりです。C標準は浮動小数点演算の動作を制御するための5つの標準プラグマを指定しています。
// c99で追加されました:
// スイッチはON、OFF、またはDEFAULTにできます
#pragma STDC FP_CONTACT ON
#pragma STDC FENV_ACCESS ON
#pragma STDC CX_LIMITED_RANGE ON
// c23で追加されました:
// 方向はFE_DYNAMIC、FE_DOWNWARD、FE_TONEAREST、
// FE_TONEARESTFROMZERO、FE_TOWARDZERO、FE_UPWARD、または
// その他の実装定義値にできます
#pragma STDC FENV_ROUND FE_DYNAMIC
#pragma STDC FENV_DEC_ROUND FE_DYNAMIC
しかし、c99が標準化されてから27年以上経っても、gccは標準プラグマのいずれもサポートしていません。それらはgccでは単なるno-op(何もしない)です。一部の機能(すべてではない)はコマンドラインフラグで再現できますが、それらは翻訳単位全体に適用されるのに対し、プラグマは個々の関数やブロックに適用できます。clangはc99プラグマのほとんどをサポートしていますが、c23プラグマはまだサポートしていません。これらについては、もっと多くのことを言うことができます。いつかそれらについてもっと書くつもりですが、今はそれが非常に大きなことなので、あまりモチベーションがありません(笑)。
関数戻り値の型修飾子
標準では、関数戻り値の型における修飾子は破棄されることが要求されています。
「T D1」という宣言において、D1がパラメータ型リスト省略可能な属性指定子シーケンス省略可能なD(パラメータ型リスト省略可能)の形式であり、「派生宣言子型リスト T」が「T D」の宣言で識別子に指定された型である場合、識別子に指定された型は「派生宣言子型リスト Tの非修飾、非アトミックバージョンを返す関数」となります。省略可能な属性指定子シーケンスは関数型に属します。
しかし、clangはそれらを重要視します。
$ cat t.c
int f();
const int f();
$ clang t.c
t.c:2:11: error: conflicting types for 'f'
2 | const int f();
| ^
t.c:1:5: note: previous declaration is here
1 | int f();
| ^
1 error generated.
2つの宣言は同一であるべきですが、clangはそれらを互換性がないと考えています。
outdatedなva_start定義(musl)
muslは、コンパイラが既に提供している一部のヘッダーの独自のバージョンを提供しています。そのうちの1つがstdarg.hで、これはフリースタンディング実装でサポートされる必要があり、そのためコンパイラによってほぼ確実に既に提供されています。しかし、muslによって提供されているため、コンパイラはそのヘッダーを使用します。これは、muslのヘッダーが組み込みヘッダーよりも厳密に劣るため問題となります。muslがva_startを定義する方法は次のとおりです。
#define va_start(v,l) __builtin_va_start(v,l)
この定義は正しくありません。なぜなら、c23では第2引数を省略することが許可されているからです。次のように定義されるべきです。
#if __STDC_VERSION__ >= 202311L
#define va_start(...) __builtin_c23_va_start(__VA_ARGS__)
#else
#define va_start(v,l) __builtin_va_start(v,l)
#endif
結果として、musl向けにコンパイルする場合、コンパイラがサポートしていても、c23の単一引数va_startを使用できません。glibcとmuslがmathエラー(math_errhandling)をどのように処理するかについての注も参照してください。
ああ、標準C言語で文字列から浮動小数点数への変換エラーをポータブルにチェックすることは不可能のようです。
POSIXが関数のファミリーを標準C言語と非互換に指定していると思われるケース
gccもclangも標準C++に準拠していません。
標準が必要としていることとは対照的に、gccとclangはC++の関数型に言語リンケージ情報を格納しません。
Linuxのmanページが不正確な別の場所(この投稿の削除されたセクションが含まれています!)