C エクステンション、ポータビリティ、代替コンパイラ

2026/05/25 23:15

C エクステンション、ポータビリティ、代替コンパイラ

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

主要な知見は、現代のソフトウェア開発において公式の ISO C 標準を完全に遵守することは稀で実用的ではないという点である。現実の世界でのプログラミングでは、重大なバグと制限に対処するために非標準的な動作やコンパイラの拡張に大いに依存しており、これらの依存関係を無視するとアプリケーションバイナリーインターフェース (ABI) が壊れてしまう。例えば、glibc のヘッダーなどの主要コンポーネントには互換性のチェックが壊れており、GCC 固有のマクロに依存している。また、

sys/epoll.h
などはパケット構造体アトリビュートを使用し、64 ビットシステムではメモリ配置を変えてしまう。主要なオペレーティングシステムはこの断片化をさらに悪化させている:Android の bionic ライブラリは Clang の使用と特定の拡張を前提としており、OpenBSD は
__only_inline
などの GCC 固有のマクロに依存している。さらに、POSIX 標準は ISO で定義されていない定数を処理するために複雑なインクルード(例えば
#include_next
)を必要とする。また、コンパイラ固有のバUILT-IN ヘッダーは各ツールチェーンで異なるパスに存在し、統合された ISO 標準への移行を試みると重大なリンカー競合に直面する。独立した C コンパイラーが存在する(tcc, cproc, scc, vbcc, nwcc, kefir, slimcc を含む)ものの、バージョンチェックやセキュリティフラグを含むプリプロセッサロジックへの深い依存は、業界全体における真のポータビリティを実際に阻害している。

本文

C コンパイラ開発における現実と非標準コードの問題点

C を扱った経験がある方であればお分かりと思いますが、完全に ISO C 標準に準拠したコードを書くのは現実的には極めて困難で、実際には稀です。世に出ている実用的な C コードの多くは、以下の理由から非標準的な挙動や言語拡張機能を利用しています。

  • バグ回避: 異なるコンパイラやライブラリの制限を回避するため。
  • 環境対応: プリプロセサチェックやガードを用いて複数の環境への対応を試みるため(しかし最善でも不安定になり、最悪の場合は壊れている)。

筆者は C コンパイラ開発の過程でこの状況を頻繁に遭遇してきました。以下に主要な事例を挙げます。


1. glibc (GNU C ライブラリ)

システム用 C ライブラリのヘッダは、有用なコンパイラを目指す者にとって最初の「障壁」です。

<stdio.h>
をプリプロセスできないと、「Hello, World!」すら動作しません。glibc の可贵な点は、非 GCC コンパイラでもヘッダの互換性を維持しようとする努力にあることです。

問題点:sys/cdefs.h と間接的依存

  • sys/cdefs.h
    は全ての libc ヘッダから間接的に包含される巨大なファイルです。
  • この中でコンパイラで定義済みマクロを確認し、対応していない拡張機能については**無効化(
    #define
    で塞ぐ)**処理が施されています。
  • しかし、この仕組み自体が壊れることがあります。

具体的な例:epoll.h と ABI 破綻

  • Linux の
    sys/epoll.h
    にある
    struct epoll_event
    はパacked 構造体であり、GNU の
    __attribute__((packed))
    を使用しています。
  • 64 ビット環境ではこれを無視すると構造体の配列が変化するため、ABI が破綻します
  • コンパイラ側でサポートを追加しても不十分です。なぜなら、前述の
    sys/cdefs.h
    に以下のコードが含まれているためです。
/* GCC、clang、互換コンパイラは '__attribute__' 構文を使って
   様々な有用な宣言を行えるが、理解できないコンパイラでは
   これらを省略しても問題ない。  */
#if !(defined __GNUC__ || defined __clang__ || defined __TINYC__)
# define __attribute__(xyz)     /* Ignore */
#endif
  • 結論: GCC、clang、あるいは tcc でなければお手上げです。
  • 補足: POSIX 標準では
    limits.h
    が C 標準の定数に加えて、POSIX 固有の定数も定義することを要求しており、コンパイラ側のヘッダの上層にプラットフォーム固有の
    limits.h
    を追加する必要があります。

limits.h の複雑な依存関係

glibc の

<limits.h>
は以下の形式で構成されています。

...
/* GNU CC でなければすべて自分で記述しなければなりません。
   他方、GCC の定義は次のように使います。  */
#if !defined __GNUC__ || __GNUC__ < 2
/* #include_next が使えないため、ANSI 仕様の <limits.h> を標準の 32 ビット単語用に定義します。
   これらは 8 ビット 'char'、16 ビット 'short int'、および 32 ビット 'int' と 'long int' を前提としています。  */
# define CHAR_BIT	8
...
# endif	/* limits.h  */
#endif	/* GCC 2.  */

#endif	/* !_LIBC_LIMITS_H_ */
 /* GCC の <limits.h> を読み込み、ほぼすべての ISO 定数を定義します。

    この #include_next を重複包含チェックの外に置くのは、このファイルを複数回 include しても
    GCC のヘッダから定義を取得できる必要があるためです。  */
#if defined __GNUC__ && !defined _GCC_LIMITS_H_
/* '_GCC_LIMITS_H_' は GCC のファイルが定義するマクロです。  */
# include_next <limits.h>
#endif
/* 一部の GCC バージョンの <limits.h> では LLONG_MIN、LLONG_MAX、ULLONG_MAX が定義されていません。 */
#if defined __USE_ISOC99 && defined __GNUC__
# ifndef LLONG_MIN
#  define LLONG_MIN	(-LLONG_MAX-1)
# endif
...
#endif

#ifdef	__USE_POSIX
/* POSIX では <limits.h> に追加の項目が定義されます。  */
# include <bits/posix1_lim.h>
#endif
...
  • このコードは、
    #include_next
    拡張機能の使用に加え、GCC 固有のビルトイン
    limits.h
    に依存して一部のマクロを正しく定義する必要があります。
  • clang でも同様の迂回策を採らざるを得ない状況です。

2. SDL

SDL_endian.h
はバイト交換関数の機能検出に奇妙なロジックを採用しています。その目的は、可能であればコンパイラ固有のビルトインやインラインアセンブリを使用し、万が一の場合のみ汎用的なビット演算 (
recourse
) することです。

実装における判断フロー

実際のコードでは以下の順序で判断されています。

  1. ビルトイン存在確認: (GCC または clang で)
    __has_builtin(__builtin_bswapX)
    が存在すれば → ビルトインを使用
  2. MSVC 特定: それ以外で(MSVC ≥ v8.0 の場合)→ MSVC 固有インライン指令
    #pragma
    を使用
  3. ISA 特定マクロ: それ以外で ISA 特定マクロ(例:
    __x86_64__
    )が定義されていれば → インラインアセンブリを使用
  4. フォールバック: その他 → 汎用的なビット演算による実装を使用
  • 問題点: GCC でも clang でもなく、ISA 特定のマクロを正当な理由で定義している場合でも、ビルトインが存在し、かつ
    __has_builtin
    オペレータも利用可能な状況であっても、拡張されたインラインアセンブリを試みるのです。
  • 未知のコンパイラが GCC スタイルの拡張型インラインアセンブリをサポートすることを期待するのは奇妙です。

3. OpenBSD libc

OpenBSD のいくつかのヘッダには、コンパイラ最適化時にオプションで使用するよう意図されたインライン関数定義が含まれています。これらは

__only_inline
マクロを用いて定義されており、例えば:

__only_inline int sigemptyset(sigset_t *__set)
{
  *__set = 0;
  return (0);
}

セマンティクスに関する混乱

  • 意図: コンパイラが実際にはインライン化しない場合に外部シンボルへのフォールバックを行うよう意図されています(「extern リンケージを持つインライン関数」)。
  • 問題: C99 規格と、GCC の C99 以前の非標準な挙動(4.2 以前までのデフォルト)が衝突し、全体として混乱を招く実装になっています。
    • ヘッダ内のインライン定義には
      extern inline
      を使い、関数本体を記述し、これによって実際のエクスポート済み関数を生成しないようにしつつ、翻訳ユニット内では単純な
      inline
      で関数を宣言してその定義をエクスポートするのが正しいはずです。
    • さらに混乱を招くのは、「inline」の意味が C++ と C では異なることです。

GCC 依存と互換性問題

  • OpenBSD は GCC の inline セマンティクスに依存しており、GCC バージョン間の差を埋めるために
    sys/cdefs.h
    __only_inline
    マクロでは、新しい GCC バージョンに対して明示的な
    __attribute__
    を使用して古式の GNU89 形式の inline セマンティクスを指定しています。
  • 一方、非 GNU コンパイラでは
    static
    リンケージとして定義され、結果的に矛盾するリンケージを持つ関数を宣言・定義してしまうため壊れてしまいます。

回避策と Gnulib

  • _ANSI_LIBRARY
    マクロを定義すれば標準ヘッダ(例:
    signal.h
    )内のこれらの壊れた
    __only_inline
    定義が完全に無視され、動作します(おそらく大きな差はありません)。
  • Guile や nano のビルド中に Gnulib の
    extern inline
    互換コードにも遭遇しました。これは C のこの隅々たるケースの破綻した・奇妙な実装を浮き彫りにしています。
#if (((defined __APPLE__ && defined __MACH__) \
      || defined __DragonFly__ || defined __FreeBSD__) \
     && (defined HAVE___HEADER_INLINE \
         ? (defined __cplusplus && defined __GNUC_STDC_INLINE__ \
            && ! defined __clang__) \
         : ((! defined _DONT_USE_CTYPE_INLINE_ \
             && (defined __GNUC__ || defined __cplusplus)) \
            || (defined _FORTIFY_SOURCE && 0 < _FORTIFY_SOURCE \
                && defined __GNUC__ && ! defined __cplusplus))))
# define _GL_EXTERN_INLINE_STDHEADER_BUG
#endif
#if ((__GNUC__ \
      ? (defined __GNUC_STDC_INLINE__ && __GNUC_STDC_INLINE__ \
         && !defined __PCC__) \
      : (199901L <= __STDC_VERSION__ \
         && !defined __HP_cc \
         && !defined __PGI \
         && !(defined __SUNPRO_C && __STDC__))) \
     && !defined _GL_EXTERN_INLINE_STDHEADER_BUG)
# define _GL_INLINE inline
# define _GL_EXTERN_INLINE extern inline
# define _GL_EXTERN_INLINE_IN_USE
#elif (2 < __GNUC__ + (7 <= __GNUC_MINOR__) && !defined __STRICT_ANSI__ \
       && !defined __PCC__ \
       && !defined _GL_EXTERN_INLINE_STDHEADER_BUG)
# if defined __GNUC_GNU_INLINE__ && __GNUC_GNU_INLINE__
   /* __gnu_inline__ は GCC 4.2 の診断を抑制します。  */
#  define _GL_INLINE extern inline __attribute__ ((__gnu_inline__))
# else
#  define _GL_INLINE extern inline
# endif
# define _GL_EXTERN_INLINE extern
# define _GL_EXTERN_INLINE_IN_USE
#else
# define _GL_INLINE _GL_UNUSED static
# define _GL_EXTERN_INLINE _GL_UNUSED static
#endif

4. bionic (Android libc)

bionic は Android の libc です。一転してヘッダが GCC よりも clang を前提しているのが大きな特徴です。

  • NULL 性チェックなどに
    _Nonnull
    _Null_unspecified1
    などの clang 固有の拡張機能を大量に使用しており、他にも多数あります。
  • 対応策: コマンドラインフラグでこれらの定義を
    #define
    して無効化するだけで対応可能です。

遭遇経緯

  • 筆者がこれに遭遇したのは、Termux をネイティブな aarch64 開発環境として Android スマホで利用していたからです(笑)。
  • その際に bionic のヘッダが使われます。

結論と展望

多くのオープンソースプロジェクトが、必須ではないことのためにコンパイラ固有の非標準拡張や挙動に依存するのは非常に面倒です。しかし同時に、あらゆる開発者が自らの C コードを複数のコンパイラ(特にマイナーまたは小規模なものを含む)でテストすることを求めるのも不公平だと言えます。C への互換性はもともと難しいものです

コンパイラ開発者への提言

以下の解決策が考えられます:

  1. 上流側での修正: 不具合の修正を試みる(勝てる戦ではない可能性が高い)。
  2. 流行り: 自らのコンパイラに対してデフォルトで専用の
    #ifdef
    チェックとテストを追加できるよう、十分な人気を得る。
  3. 下流側での対応: パッチを配布する(最も容易)。
  4. 偽装実装: GCC の一部のバージョンを装ってその拡張機能を実装する。
  • (4) 的手法の例: clang は GCC 4.2.1 と互換性を持つと主張するために
    __GNUC__=4
    (そして
    __GNUC_MINOR__=2
    __GNUC_PATCHLEVEL__=1
    )を定義しています。
  • 問題点: 多くのコードベースが
    #ifdef __GNUC__
    をチェックし、そのマクロが定義されていればあらゆる最新の GCC 拡張機能を自由に使用します(バージョンチェックなし)。そのため追いかけっこを続けることになり、clang が新しい GNU 拡張機能をサポートしているにもかかわらず
    __GNUC__
    マクロを更新しない一因となっています。

理想とする状態

  • 機能テストマacro (
    __has_builtin
    ,
    __has_feature
    ,
    __has_attribute
    ) や標準マクロ (
    __STDC_NO_VLA__
    ) が広く採用され、コンパイラ固有のガードやバージョンチェックに代わるべきです。

現時点では、良いも悪しきものの NIX 世界での地位は GCC/clang の準独占状態になっています。小さな独立系 C コンパイラ開発者の方々(tcc, cproc, scc, vbcc, nwcc, kefir など)には拍手を送りたいと思います。


※ 詳細は lobsters での議論や slimcc の開発者による事例を参照してください。

同じ日のほかのニュース

一覧に戻る →

2026/05/26 2:45

Exit IP VPN サーバー対策の展開

## 日本語訳: 2026 年 5 月 25 日付で、12 つの特定サーバー識別子への新しい緩和措置の適用を含む重要な運用上のアップデートが確認されました。この措置は、北米、ヨーロッパ、オセアニアを含む主要なグローバル地域にわたるインフラに影響します。影響を受けたサーバーは、以下の通り明示的にリストされています:au-mel-wg-402, au-syd-wg-001, ca-mtr-wg-302, de-fra-wg-103, fi-hel-wg-201, fr-par-wg-101, ie-dub-wg-101, no-osl-wg-101, se-sto-wg-208, us-dal-wg-701, us-lax-wg-002, us-nyc-wg-601, us-slc-wg-303。この実装は、内部での判断を踏まえたこれらのエンドポイントの状態における決定的な変化を表します。このアナウンスメントでは、識別子のリストとアップデートの日付のみが提供されており、脅威の具体的な性質、先行文脈、またはエンドユーザーおよび産業エンティティに対する直接的な影響について言及していません。したがって、この変更の原因や以降のタイムラインに関するさらなる説明はまだ発表されていません。 ## 原文: **Improved Summary:** Effective May 25, 2026, a significant operational update has been confirmed involving the application of a new mitigation to twelve specific server identifiers. This action impacts infrastructure across major global regions, including North America, Europe, and Oceania. The affected servers are explicitly listed as: au-mel-wg-402, au-syd-wg-001, ca-mtr-wg-302, de-fra-wg-103, fi-hel-wg-201, fr-par-wg-101, ie-dub-wg-101, no-osl-wg-101, se-sto-wg-208, us-dal-wg-701, us-lax-wg-002, us-nyc-wg-601, and us-slc-wg-303. This implementation marks a definitive shift in the status of these endpoints following an internal decision. The announcement provides only the list of identifiers and the update date; it does not elaborate on the specific nature of the threat, prior context, or direct impacts on end-users and industry entities. Consequently, further clarification regarding the reasons for this change or subsequent timelines has not yet been released.

2026/05/26 4:37

ノルウェーのHuaweiフラッシュストレージによる2ペタバイトとLLMトレーニング

## 日本語訳: ノルウェー国立図書館は、地元のノルウェー語に対する商用ソリューションの深刻な不足に対応するため、自らのデジタルアーカイブにのみ基づいて訓練された主権性の高い大規模言語モデルを起動する予定である。このプロジェクトは文化省によって推進され、IT 責任者マリウス・フスネス氏の率いるものであり、私企業の手に負えない著作権保護付き新聞への図書館特有の法的アクセスを活用して、約 60 ピタバイトのデジタル化された遺産(現在は 3-2-1 保存形式で保管されている)をこの AI に訓練している。 技術的なアーキテクチャでは、3 つの異なるシステムをオーケストレーションするものであり、初期処理段階には Nvidia DGX H200 クラスターと华为(Huawei)OceanStor Dorado フルフラッシュアレイ(高速フラッシュ容量 2 PB を提供)を用い、その後、最終的な訓練は国立スーパーコンピューター Sigma2 Olivia で行われる。主要なボトルネックは計算能力ではなく、データの品質、クリーニング、パイプラインのスループットであり、これは AI パイプラインの低遅延要求と大規模かつ高遅延の保存アーカイブとの間の遅延不一致によって複雑化している。さらに、使用制御のためのガバナンス枠組みの確立や標準的な評価ツールの利用という課題にも直面している。このイニシアチブにより、ノルウェーは外国のテクノロジー大手に依存せずに技術的未来を確保することができ、他の英語圏外における国々が主権性の高い AI 基盤を求めるためのモデルとして挙げられている戦略となっている。

2026/05/26 3:19

カリフォルニア州、Linuxの年齢認証免除を法改正で検討へ:抗議運動を受け

## Japanese Translation: カリフォルニア州の法律家は、2026年2月11日にデジタル年代保証法(Digital Age Assurance Act)からほとんどのオープンソースオペレーティングシステムを除外することを目的としたアセンブリビル1856号(AB 1856)を導入した後、厳格な年齢検証要件から撤退する方針を検討しています。AB 1856は、同州の以前のパックであるアセンブリビル1043号(2025年後期に可決)を改正し、2027年1月1日までに年齢検証の手続を個々のウェブサイトやアプリからオペレーティングシステムレベルへ移行することを求めていました。AB 1043の下では、オペレーティングシステムはセットアップ時にユーザーの生年月日を収集し、アプリおよびストアに対して「年齢層」(例:「13歳未満」、「18歳以上」)をシグナル送信することが義務付けられていました。提案されている改正案では、「オペレーティングシステム提供者」は、ソフトウェアのコピー、再配布、修改を可能にするライセンスの下でソフトウェアを配布する者を含まないと定義しています。この定義により、Debian、Fedora、Ubuntu、Arch Linux、Mintなど主流のLinuxディストリビューションが遵守要件から除外され、Electronic Frontier Foundationなどのプライバシー擁護者や、無限にフォーク可能なプロジェクトに対しては元の規則が侵襲的かつ実行不可能であるという懸念に対処しました。同法案は元の法的措置を廃止するものではありません。商用プラットフォームでプロプライエタリなアプリエコシステムを持つもの(ValveのSteamストアに紐づく特定のSteamOSバージョンなど)も引き続き審査の対象となる可能性があります。アセンブリメンバーBuffy Wicksによって提出された最新版は2026年5月18日付けのものであり、2026年5月19日に2回目に朗読され、3回目の朗読に付議されるよう命じられ、現在、委員会での審査が予定されている6月の間に立法機構を通過中としています。