0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[C言語] 同じソースでC90/C99/C11/C23の挙動を変えてみる

Posted at

はじめに

  • C言語の規格ごとに異なる結果を出力するプログラムを作ってみます
  • __STDC_VERSION__ 等で動作を切り替えるのではなく、規格ごとの差をもとに切り替えます
  • 間違いとか、もっと良い方法とかあれば教えてください、プリーズ
  • タイトルに C23 と書いているけど、実際には試していません。ごめんなさい
  • たぶん、役に立たない情報です

昔、似たような記事を読んだ記憶があって、少し探してみましたが多分以下の記事かな?

C言語の規格

とりあえずC言語の規格と、今回関係ありそうな変更箇所を軽く復習してみます。

K&R

規格というよりも、最初のC言語、およびその後の各種派生バージョン(後述の ANSI C 以前のもの)のことをまとめて呼ぶ感じ?

1978年に発行された『The C Programming Language』の著者名(Brian W. KernighanDennis M. Ritchie)が由来。

関数や引数の定義方法なども違うし、現在 K&R 用のコンパイラもなさそう(本当?)なので、今回は対象外とします。

C90 - ISO/IEC 9899:1990

1989年にANSIが標準化して、1990年にISOも同等のものを発行したもの。

後の規格と区別するため、C89あるいはC90と略されるらしい。今回はISO側に寄せて C90 と呼びます。

いわゆる「ANSI C」ですね。いや、厳密にいえば ANSI として改訂されたこの後の規格も「ANSI C」なのでしょうけど、単に「ANSI C」と呼ぶときは、多くの場合この規格のことをさしている気がします。

  • ANSI X3.159-1989, American National Standard for Information Systems -Programming Language-C
  • ISO/IEC 9899:1990(E) Programming Languages - C
  • JIS X 3010-1993 プログラム言語C

特徴は、関数のプロトタイプ宣言や型のチェックが(K&Rと比べて)強化されているところだと思います。

今回のベースの規格になります。

C95 - ISO/IEC 9899/AMD1:1995

ISOが若干の規格の拡張を行ったのもの。

言語的にはほぼ同じだけど(ダイグラフが追加されたぐらい?)、ライブラリやマクロの定義が追加された。 __STDC_VERSION__ マクロもここで初めて定義されたはず。
そのため __STDC_VERSION__ マクロが定義されているかをチェックすればC90との見分けはつくけど、今回の主題の規格の差による挙動の違いはなさそう?

C99 - ISO/IEC 9899:1999

1999年に ISO が規格の改定を行って、C++の機能の一部を取り込むなどしたもの。

いくつか変更点はあるようですが、今回使おうと思っている変更点は行コメントの追加です。C++のように // コメント で行コメントを書けるようになりました。

C言語のトークン分割は、基本的には最大一致で処理されます。その結果、改訂された規格で新しいトークンが増えた場合に、ごくまれに互換性が破壊される可能性があります。

例えば //**/ のようなコーディングは C90 では除算演算子 / とコメント /**/ に扱われますが、C99 では先頭の // が行コメントとして扱われます。

つまり、以下のような記述で行コメントが有効か無効かを判定できます(つまり C90 と C99 以降の区別がつく)。

#include <stdio.h>

int main() {
    int is_enabled_line_comment = 1//**/1-1
    ;
    printf("is_enabled_line_comment: %d\n", is_enabled_line_comment);
    return 0;
}

C90では 1/1-1 つまり 0 となり、C99以降であれば 1 になります。

C11 - ISO/IEC 9899:2011

2011年に改訂された規格。

今回使えそうな変更はUnicode系文字列リテラルの追加ですね。

新しく追加されたUnicode系の文字列リテラルは、例えば U"1234" のような表記で1つの文字列トークンになりますが、C99までは U"1234" の2つのトークンだと判断されます(そして、何もしなければシンタックスエラーになるはず)。

つまり以下のようなコードで、Unicode系文字列リテラルが有効かを確認できます( #define はトークン単位で置換するため。そしてC言語で文字列リテラルを並べて書くと字句解析時に結合されるので)。

#include <stdio.h>
#define u "u"
#define U "U"
#define u8 "u8"

int main() {
    int is_enabled_utf8 = u8"1"[0] == '1';
    int is_enabled_utf16 = u"1"[0] == '1';
    int is_enabled_utf32 = u"1"[0] == '1';
    printf("is_enabled_utf8: %d\n", is_enabled_utf8);
    printf("is_enabled_utf16: %d\n", is_enabled_utf16);
    printf("is_enabled_utf32: %d\n", is_enabled_utf32);
    return 0;
}

ちなみに、C11には関係ないですがC90でサポートされたワイド文字リテラル L"abc" や、C++でサポートされている Raw文字リテラル R"(abc)" なども同じ方法でチェックが可能です。

C17 - ISO/IEC 9899:2018

C11の仕様(規格)の問題を修正したもので、言語仕様的にはほぼ変わらないはず。

__STDC_VERSION__ の値は変わっているため見分けはつくけど、今回の主題の規格の差による挙動の(C11との)違いはなさそう。

2018年に発行されたけど __STDC_VERSION__201710L なところから、C17 と呼称されているらしい。でも、たまに C18 と呼ばれることもあるみたい(少なくとも gcc のオプションでは -std=c18 が別名で、 -std=c17 が正式名っぽいメッセージになっている)。

C23 - ISO/IEC 9899:2024

2024年に発行された、現時点での最新規格。

いろいろ変わったみたいだけど、今回関係しそうな話としては、以下?

  • トライグラフが正式に廃止された
  • K&R形式の関数定義ができなくなった

トライグラフは ??=# を表すような、特定の3文字を1文字とみなすものですが、これはトークンや文字列内などを問わずに書き換えられます(プリプロセッサよりも前に処理される)。

#include <stdio.h>

int main() {
    int is_enabled_trigraph = "??="[0] == '#';
    printf("is_enabled_trigraph: %d\n", is_enabled_trigraph);
    return 0;
}

(ちなみに、たぶんC95でサポートされたのだと思いますが、ダイグラフというやつも似ていて <:[ を表すようなものですが、文字列内は置換の対象外であり、また最長一致のトークン分割の例外にもなっています。なので規格の切り分けに使うのはちょっと難しそう?)。

C++言語の規格

おまけで、C++言語の規格についても簡単におさらい。

  • C++98 : 1998年に標準化された ISO/IEC 14882:1998
  • C++03 : 2003年に改訂された ISO/IEC 14882:2003 。基本は規格の不具合対策版? C++98 とほぼ同じ
  • C++11 : 2011年に改訂された ISO/IEC 14882:2011 。策定前は C++0x と呼ばれていた
  • C++14 : 2014年に改訂された ISO/IEC 14882:2014 。策定前は C++1y とよばれていた
  • C++17 : 2017年に改訂された ISO/IEC 14882:2017 。策定前は C++1z とよばれていた
  • C++20 : 2020年に改訂された ISO/IEC 14882:2020 。策定前は C++2a とよばれていた?
  • C++23 : 2024年に改訂された ISO/IEC 14882:2024 。策定前は C++2b とよばれていた?

C言語とC++言語はある程度の互換性があり簡単なC言語のソースはC++コンパイラでもコンパイルできますが、いくつかの違いがあります。例えば文字リテラル( 'a' など)は C 言語では int 型になるので sizeof('a')int のバイト数が返ってきますが(例えば32bit環境やLP64の64ビット環境なら4バイト、ILP64なら8バイトになると思う)、C++言語では char になるので 1 が返ってきます。

#include <stdio.h>

int main() {
    int sizeof_char = sizeof('a');
    printf("sizeof_char: %d\n", sizeof_char);
    return 0;
}

動作確認

ソースコードをまとめて(ついでに少し増やして)みました。

#include <stdio.h>
#define L "L"
#define u "u"
#define U "U"
#define u8 "u8"
#define R "R"

int main() {
    int is_enabled_line_comment = 1//**/1-1
    ;
    int is_enabled_utf8 = u8"1"[0] == '1';
    int is_enabled_utf16 = u"1"[0] == '1';
    int is_enabled_utf32 = u"1"[0] == '1';
    int is_enabled_wchar = L"1"[0] == '1';
    int is_enabled_raw = R"(1)"[0] == '1';
    int is_enabled_trigraph = "??="[0] == '#';
    int sizeof_char = sizeof('a');

    printf("is_enabled_line_comment: %d\n", is_enabled_line_comment);
    printf("is_enabled_utf8: %d\n", is_enabled_utf8);
    printf("is_enabled_utf16: %d\n", is_enabled_utf16);
    printf("is_enabled_utf32: %d\n", is_enabled_utf32);
    printf("is_enabled_wchar: %d\n", is_enabled_wchar);
    printf("is_enabled_raw: %d\n", is_enabled_raw);
    printf("is_enabled_trigraph: %d\n", is_enabled_trigraph);
    printf("sizeof_char: %d\n", sizeof_char);
#ifdef __STDC_VERSION__
    printf("__STDC_VERSION__: %ldL\n", __STDC_VERSION__);
#endif
#ifdef __cplusplus
    printf("__cplusplus: %ldL\n", __cplusplus);
#endif
    return 0;
}

試しに、gcc で動作確認をしてみます。

gcc では -std=XXX というオプション( XXX は規格名など)で、指定の規格でのコンパイルが可能なようです。

gcc -v --help | grep '^ -std=' として、使える規格名を調べてみると以下のようになっていました(なんだか、gcc なのに g++ や fortran のオプションも引っかかるようですが、とりあえず gcc/g++のみに絞りました。また grep しているせいで、一部のオプションが途中で切れていますが)。

  -std=c++03                  Conform to the ISO 1998 C++ standard revised by the 2003 technical corrigendum.  Same as -std=c++98.
  -std=c++0x                  Deprecated in favor of -std=c++11.  Same as -std=c++11.
  -std=c++11                  Conform to the ISO 2011 C++ standard.
  -std=c++14                  Conform to the ISO 2014 C++ standard.
  -std=c++17                  Conform to the ISO 2017 C++ standard.
  -std=c++1y                  Deprecated in favor of -std=c++14.  Same as -std=c++14.
  -std=c++1z                  Deprecated in favor of -std=c++17.  Same as -std=c++17.
  -std=c++20                  Conform to the ISO 2020 C++ standard (experimental and incomplete support).
  -std=c++23                  Conform to the ISO 2023 C++ draft standard (experimental and incomplete support).
  -std=c++2a                  Conform to the ISO 2020 C++ standard (experimental and incomplete support).  Same as -std=c++20.
  -std=c++2b                  Conform to the ISO 2023 C++ draft standard (experimental and incomplete support).  Same as -std=c++23.
  -std=c++98                  Conform to the ISO 1998 C++ standard revised by the 2003 technical corrigendum.
  -std=c11                    Conform to the ISO 2011 C standard.
  -std=c17                    Conform to the ISO 2017 C standard (published in 2018).
  -std=c18                    Conform to the ISO 2017 C standard (published in 2018).  Same as -std=c17.
  -std=c1x                    Deprecated in favor of -std=c11.  Same as -std=c11.
  -std=c2x                    Conform to the ISO 202X C standard draft (experimental and incomplete support).
  -std=c89                    Conform to the ISO 1990 C standard.  Same as -std=c90.
  -std=c90                    Conform to the ISO 1990 C standard.
  -std=c99                    Conform to the ISO 1999 C standard.
  -std=c9x                    Deprecated in favor of -std=c99.  Same as -std=c99.
  -std=gnu++03                Conform to the ISO 1998 C++ standard revised by the 2003 technical corrigendum with GNU extensions.  Same as
  -std=gnu++0x                Deprecated in favor of -std=gnu++11.  Same as -std=gnu++11.
  -std=gnu++11                Conform to the ISO 2011 C++ standard with GNU extensions.
  -std=gnu++14                Conform to the ISO 2014 C++ standard with GNU extensions.
  -std=gnu++17                Conform to the ISO 2017 C++ standard with GNU extensions.
  -std=gnu++1y                Deprecated in favor of -std=gnu++14.  Same as -std=gnu++14.
  -std=gnu++1z                Deprecated in favor of -std=gnu++17.  Same as -std=gnu++17.
  -std=gnu++20                Conform to the ISO 2020 C++ standard with GNU extensions (experimental and incomplete support).
  -std=gnu++23                Conform to the ISO 2023 C++ draft standard with GNU extensions (experimental and incomplete support).
  -std=gnu++2a                Conform to the ISO 2020 C++ standard with GNU extensions (experimental and incomplete support).  Same as
  -std=gnu++2b                Conform to the ISO 2023 C++ draft standard with GNU extensions (experimental and incomplete support).  Same
  -std=gnu++98                Conform to the ISO 1998 C++ standard revised by the 2003 technical corrigendum with GNU extensions.
  -std=gnu11                  Conform to the ISO 2011 C standard with GNU extensions.
  -std=gnu17                  Conform to the ISO 2017 C standard (published in 2018) with GNU extensions.
  -std=gnu18                  Conform to the ISO 2017 C standard (published in 2018) with GNU extensions.  Same as -std=gnu17.
  -std=gnu1x                  Deprecated in favor of -std=gnu11.  Same as -std=gnu11.
  -std=gnu2x                  Conform to the ISO 202X C standard draft with GNU extensions (experimental and incomplete support).
  -std=gnu89                  Conform to the ISO 1990 C standard with GNU extensions.  Same as -std=gnu90.
  -std=gnu90                  Conform to the ISO 1990 C standard with GNU extensions.
  -std=gnu99                  Conform to the ISO 1999 C standard with GNU extensions.
  -std=gnu9x                  Deprecated in favor of -std=gnu99.  Same as -std=gnu99.
  -std=iso9899:1990           Conform to the ISO 1990 C standard.  Same as -std=c90.
  -std=iso9899:199409         Conform to the ISO 1990 C standard as amended in 1994.
  -std=iso9899:1999           Conform to the ISO 1999 C standard.  Same as -std=c99.
  -std=iso9899:199x           Deprecated in favor of -std=iso9899:1999.  Same as -std=c99.
  -std=iso9899:2011           Conform to the ISO 2011 C standard.  Same as -std=c11.
  -std=iso9899:2017           Conform to the ISO 2017 C standard (published in 2018).  Same as -std=c17.
  -std=iso9899:2018           Conform to the ISO 2017 C standard (published in 2018).  Same as -std=c17.

残念ながら C23 は対応していないみたい(手元の gcc が古い。現時点の最新の gcc 15.2 等ではサポートされているはず)ですが、この範囲で動作確認をしてみます。
重複分を除外すると

  • C 言語が c90 iso9899:199409 c99 c11 c17 c2x gnu90 gnu99 gnu11 gnu17 gnu2x
  • C++ が c++98 c++11 c++14 c++17 c++20 c++23 gnu++98 gnu++11 gnu++14 gnu++17 gnu++20 gnu++23

でしょうか。C95の簡単な指定はないみたいなので ISO 規格名( iso9899:199409 )を指定します。gnuから始まるのは独自拡張ありですね。まとめて実行してみます。

#!/bin/sh

for std in c90 iso9899:199409 c99 c11 c17 c2x gnu90 gnu99 gnu11 gnu17 gnu2x; do
    echo "-----------------"
    echo "$std"
    gcc -std="$std" a.c && ./a.out
done
for std in c++98 c++11 c++14 c++17 c++20 c++23 gnu++98 gnu++11 gnu++14 gnu++17 gnu++20 gnu++23; do
    echo "-----------------"
    echo "$std"
    g++ -std="$std" a.c && ./a.out
done
echo "-----------------"

実行結果:

-----------------
c90
is_enabled_line_comment: 0
is_enabled_utf8: 0
is_enabled_utf16: 0
is_enabled_utf32: 0
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 1
sizeof_char: 4
-----------------
iso9899:199409
is_enabled_line_comment: 0
is_enabled_utf8: 0
is_enabled_utf16: 0
is_enabled_utf32: 0
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 1
sizeof_char: 4
__STDC_VERSION__: 199409L
-----------------
c99
is_enabled_line_comment: 1
is_enabled_utf8: 0
is_enabled_utf16: 0
is_enabled_utf32: 0
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 1
sizeof_char: 4
__STDC_VERSION__: 199901L
-----------------
c11
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 1
sizeof_char: 4
__STDC_VERSION__: 201112L
-----------------
c17
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 1
sizeof_char: 4
__STDC_VERSION__: 201710L
-----------------
c2x
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 1
sizeof_char: 4
__STDC_VERSION__: 202000L
-----------------
gnu90
a.c: In function ‘main’:
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 0
is_enabled_utf16: 0
is_enabled_utf32: 0
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 0
sizeof_char: 4
-----------------
gnu99
a.c: In function ‘main’:
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 4
__STDC_VERSION__: 199901L
-----------------
gnu11
a.c: In function ‘main’:
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 4
__STDC_VERSION__: 201112L
-----------------
gnu17
a.c: In function ‘main’:
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 4
__STDC_VERSION__: 201710L
-----------------
gnu2x
a.c: In function ‘main’:
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 4
__STDC_VERSION__: 202000L
-----------------
c++98
is_enabled_line_comment: 1
is_enabled_utf8: 0
is_enabled_utf16: 0
is_enabled_utf32: 0
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 1
sizeof_char: 1
__cplusplus: 199711L
-----------------
c++11
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 1
sizeof_char: 1
__cplusplus: 201103L
-----------------
c++14
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 1
sizeof_char: 1
__cplusplus: 201402L
-----------------
c++17
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 201703L
-----------------
c++20
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 202002L
-----------------
c++23
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 202100L
-----------------
gnu++98
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 0
is_enabled_utf16: 0
is_enabled_utf32: 0
is_enabled_wchar: 1
is_enabled_raw: 0
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 199711L
-----------------
gnu++11
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 201103L
-----------------
gnu++14
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 201402L
-----------------
gnu++17
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 201703L
-----------------
gnu++20
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 202002L
-----------------
gnu++23
a.c:16:32: warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
   16 |     int is_enabled_trigraph = "??="[0] == '#';
      |                                 
is_enabled_line_comment: 1
is_enabled_utf8: 1
is_enabled_utf16: 1
is_enabled_utf32: 1
is_enabled_wchar: 1
is_enabled_raw: 1
is_enabled_trigraph: 0
sizeof_char: 1
__cplusplus: 202100L
-----------------
規格 行コメント Unicode文字列 ワイド文字列 Raw文字列 トライグラフ sizeof('a') __STDC_VERSION__
c90 4 なし
iso9899:199409 4 199409L
c99 4 199901L
c11 4 201112L
c17 4 201710L
c2x 4 202000L
規格 行コメント Unicode文字列 ワイド文字列 Raw文字列 トライグラフ sizeof('a') __STDC_VERSION__
gnu90 ✕(W) 4 なし
gnu99 ✕(W) 4 199901L
gnu11 ✕(W) 4 201112L
gnu17 ✕(W) 4 201710L
gnu2x ✕(W) 4 202000L
規格 行コメント Unicode文字列 ワイド文字列 Raw文字列 トライグラフ sizeof('a') __cplusplus
c++98 1 199711L
c++11 1 201103L
c++14 1 201402L
c++17 ✕(W) 1 201703L
c++20 ✕(W) 1 202002L
c++23 ✕(W) 1 202100L(!)
規格 行コメント Unicode文字列 ワイド文字列 Raw文字列 トライグラフ sizeof('a') __cplusplus
gnu++98 ✕(W) 1 199711L
gnu++11 ✕(W) 1 201103L
gnu++14 ✕(W) 1 201402L
gnu++17 ✕(W) 1 201703L
gnu++20 ✕(W) 1 202002L
gnu++23 ✕(W) 1 202100L(!)

トライグラフの (W) は警告が出るかどうかです。
また、c++23 / gnu++23 の __cplusplus の値はおかしい気がしますが、手元の g++ が古いためでしょうね(オプションの説明もドラフト版とあったので)。

まとめると、

  • c90 と c95 は __STDC_VERSION__ を見ないと わからなそう
  • c90/c95 と c99 の差は行コメントでわかりそう
  • c99 と c11 はUnicode文字列の有無でわかりそう
  • c11 と c17 は __STDC_VERSION__ を見ないと わからなそう
  • c23 は 確認できていないけど 、トライグラフの有無を見ればわかりそう?
  • c90 と gnu90 は、トライグラフの有無でわかりそう
  • gnu90 と gnu99以降はUnicode文字列の有無でわかりそう
  • gnu99以降の拡張規格は __STDC_VERSION__ を見ないと 区別がつかなそう
  • おそらく c99~c23 と gnu17~gnu23 は Raw文字列 ( R"(XXXX)" 。本来はC++にしか無い機能 ) の有無でわかりそう

おまけで C++ については(C言語と共通のソース、かつ #if などで分離しないという前提だと。c++専用ソースならもう少し分離できそう?)

  • C と C++ は sizeof('a') などの差異があるので見分けはつきそう
  • c++98 と c++11 以降は Unicode 文字の有無でわかりそう
  • c++11 と c++14 は __cplusplus を見ないとわからなそう
  • c++14 と c++17 はトライグラフの有無を見ればわかりそう
  • c++17 以降は __cplusplus を見ないとわからなそう
  • c++98/c++11/c++14とgnu++98/gnu++11/gnu++14は、トライグラフの有無でわかりそう
  • gnu++11 以降は __cplusplus を見ないとわからなそう

本来なら、gcc だけじゃなくて clang とか MSVC とかも調べたほうが良いのかもしれないですが、面倒なので省略します。

というわけで、 標準のC言語規格でコンパイルされている場合 (拡張機能なしでコンパイルされている場合)に、その規格を表示するプログラム(仮)です。C++についてはあまりわからないですが、少しだけ判定を入れてみました。

#include <stdio.h>
#define U "U"
#define R "R"

int main() {
    int is_enabled_line_comment = 1//**/1-1
    ;
    int is_enabled_utf32 = U"1"[0] == '1';
    int is_enabled_raw = R"(1)"[0] == '1';
    int is_enabled_trigraph = "??="[0] == '#';
    int sizeof_char = sizeof('a');

    if (sizeof_char == sizeof(int)) {
        if (is_enabled_raw) {
            printf("It is unknown what C standard it was compiled with.\n");
        } else if (!is_enabled_line_comment) {
            printf("It was probably compiled using the C90 or C95 standard.\n");
        } else if (!is_enabled_utf32) {
            printf("It was probably compiled using the C99 standard.\n");
        } else if (is_enabled_trigraph) {
            printf("It was probably compiled using the C11 or C17 standard.\n");
        } else {
            printf("It was probably compiled to the C23 standard.\n");
        }
    } else {
        if (!is_enabled_utf32) {
            printf("It was probably compiled using the C++98 standard.\n");
        } else if (is_enabled_trigraph) {
            printf("It was probably compiled using the C++11 or C++14 standard.\n");
        } else {
            printf("It was probably compiled using the C++17, C++20 or C++23 standard.\n");
        }
    }
#ifdef __STDC_VERSION__
    printf("__STDC_VERSION__: %ldL\n", __STDC_VERSION__);
#endif
#ifdef __cplusplus
    printf("__cplusplus: %ldL\n", __cplusplus);
#endif
    return 0;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?