1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C++をC言語の上位互換たらしめているものの正体

Last updated at Posted at 2022-06-12

概要

C++はC言語の上位互換である、というなんとなくの認識でこれまで実装していました。
ですが、それぞれの言語の言語仕様の対応状況を調査した結果、コンパイラが上手く隠してくれている部分があると分かりました。
その結果をもとに、前回の記事で取り上げたパーサーのパス率向上につなげました。
これらが何かの一助となれば幸いです。

環境やバージョン

  • Windows10 Pro
  • g++ 11.2.0(cygwin経由)

言語仕様

プログラミング言語には通常、その言語毎に仕様が定義されています。言語仕様については

を参照してください。私の雑な理解では、こう文字を入力すると、コンピュータはそれに基づいて計算して、こんな結果を返します、ということが事細かに定義されたものです。もちろん、C言語にもC++にも言語仕様はあり、それらは定期的に更新されています。
いずれも最新ではないものの、今回の記事の内容に関係するものとして、C言語であればC99の言語仕様はここから、C++であればC++14の言語仕様はここから参照できます。

初期化指示子

さて、前回構文解析したESP32のソースコードのうち、
arduino-esp32-master\cores\esp32\FirmwareMSC.cpp
でこんなエラーがANTLRのruntimeから出ていました。

line 77:6 mismatched input '.' expecting '}'
line 78:6 no viable alternative at input '.'

該当する行を含むブロックを示すと

76: const esp_partition_pos_t running_pos  = {
77:       .offset = partition->address,
78:       .size = partition->size,
79: };

こうなっています。これは、指示初期化子と呼ばれるもので、構造体において変数を宣言しつつ、各メンバをドットで指定し、かつ、代入演算子で初期値を指定する、というもので、言語仕様では指示初期化子という名称で定義されているようです。この書き方を知って以降、当たり前のように使っているのですが、改めてC言語の言語仕様を調べてみると、

C99から導入されているとわかりました。一方、C++の言語仕様によると

導入はC++20であることが分かりました。C++での日本語名はここでは指示付き初期化とされています。翻訳した人の違いか、C++では同じに見えて若干意味合いが違うのか、定かではありませんが、以降の説明では指示初期化子で統一します。

g++でなぜエラーにならない?

C++の言語仕様での対応状況から、指示初期化子はC++20から導入にも関わらずESP32のソースコード
arduino-esp32-master\cores\esp32\FirmwareMSC.cpp
がESP32開発元でコンパイルできているのはなぜか、g++を使ってその理由を探ります。

言語仕様のバージョン確認

インストール済みのg++はcygwin経由で導入したもので、バージョン11.2.0です。
導入したg++がC++のどのバージョンの言語仕様をデフォルトとしているかを確かめる方法は

が詳しく、ここで紹介されている方法を試したところ、
define __cplusplus 201703L
と出たきたので、C++17とみていいでしょう。
ますますエラーにならない理由がわかりません。

コンパイルの準備

ということであれば、実際にコンパイルしてみるまでです。
ただし、コンパイラによって実行形式ファイルを作成したいわけではなく、コンパイルすることが目的なので、ここでは中間ファイルの生成ができれば良いです。

を参考に、中間ファイルを生成する-cオプションを使用します。
詳細は省略しますが、

  1. コンパイルに必要なdefineの調査と指定
  2. 不足しているinclude pathの調査と指定
  3. 厳格な型の不適合を指摘するオプションの無効化

などの細かい作業によってコンパイルできる状況までエラーを抑え込んだ結果、cygwinのコマンドプロンプトに入力した内容は次のとおりになりました。

コマンドプロンプト
g++ -fpermissive -I"variants/esp32" -I"tools/sdk/esp32/include/driver/include" -I"tools/sdk/esp32/include/log/include" -I"tools/sdk/esp32/include/bootloader_support/include" -I"tools/sdk/esp32/include/app_update/include" -I"tools/sdk/esp32/include/spi_flash/include" -I"tools/sdk/esp32/include/lwip/include/apps" -I"tools/sdk/esp32/include/lwip/port/esp32/include" -I"tools/sdk/esp32/include/lwip/lwip/src/include" -I"tools/sdk/esp32/include/tcpip_adapter/include" -I"tools/sdk/esp32/include/esp_eth/include" -I"tools/sdk/esp32/include/esp_netif/include" -I"tools/sdk/esp32/include/esp_wifi/include" -I"tools/sdk/esp32/include/heap/include" -I"tools/sdk/esp32/include/newlib/platform_include" -I"tools/sdk/esp32/include/esp_timer/include" -I"tools/sdk/esp32/include/esp_system/include" -I"tools/sdk/esp32/include/soc/include" -I"tools/sdk/esp32/include/hal/esp32/include" -I"tools/sdk/esp32/include/hal/include" -I"tools/sdk/esp32/include/esp_hw_support/include" -I"tools/sdk/esp32/include/soc/esp32/include" -I"tools/sdk/esp32/include/freertos/include/esp_additions/freertos" -I"tools/sdk/esp32/include/freertos/include/esp_additions" -I"tools/sdk/esp32/include/esp_rom/include" -I"tools/sdk/esp32/include/xtensa/esp32/include" -I"tools/sdk/esp32/include/xtensa/include" -I"tools/sdk/esp32/include/freertos/port/xtensa/include" -I"tools/sdk/esp32/include/freertos/include" -I"tools/sdk/esp32/include/config" -I"tools/sdk/esp32/include/esp_event/include" -I"tools/sdk/esp32/include/esp_common/include" -DCONFIG_TINYUSB_MSC_ENABLED -c cores/esp32/FirmwareMSC.cpp

当該ファイル中の一部箇所においてC++20を必要とする実装があったので、一部手直しした上で、次の検討に臨んでいます。

オプション指定で確かめる

ここまででやはりコンパイルできる状況となりました。
検討の準備が整ったので、ここからはg++のオプションをいくつか変更して、コンパイルの可否についてみてみます。
事前に検討したところから対象のオプションは、stdpedantic-errorsとしました。pedantic-errorsはGCC拡張を無効にするオプションで、それについてはこちらの記事が詳しいです。
指示初期化子におけるコンパイル時のエラーの有無をまとめたものが以下になります。

std=c++20 std=c++17 std=c++14 std指定なし
pedantic-errors指定なし エラーなし エラーなし エラーなし エラーなし
pedantic-errors指定あり エラーなし エラーあり エラーあり エラーあり

参考までにg++が出力するエラー内容は次のようになります。

エラー内容
cores/esp32/FirmwareMSC.cpp:77:7: エラー: C++ designated initializers only available with ‘-std=c++20’ or ‘-std=gnu++20’ [-Wpedantic]
   77 |       .offset = partition->address,
      |       ^
cores/esp32/FirmwareMSC.cpp:78:7: エラー: C++ designated initializers only available with ‘-std=c++20’ or ‘-std=gnu++20’ [-Wpedantic]
   78 |       .size = partition->size,
      |       ^

pedantic-errors指定なし、つまり、GCC拡張が有効であることで、C++20指定でなくとも指示初期化子のコンパイルでエラーがでないとわかります。この結果から、g++はC99に基づいて実装されたソースコードであってもコンパイルできるように配慮した設計となっている、ということが実感できました。
この結果だけを持って言うのは大げさかもしれませんが、C++をC言語の上位互換たらしめているものの正体はコンパイラである、といえるのではないでしょうか。

パス率改善へ

以上を踏まえ、GCC拡張の一部を前回作成したパーサーに組み込めば、パス率の改善が見込めそうです。
ここからは前回取り上げたパーサーの変更について説明します。

CPP14パーサ + GCC拡張の一部 = ?

ところで、CPP14パーサにGCC拡張の一部を足したものはなんと呼ぶべきでしょうか。無理に名前をつける必要はありませんが、いろいろ調べた結果、

にある、C++14 Support in GCCが良さそうなので、以降はそれを使うことにします。

initializerを見直す

C++14 Support in GCCとなるようCPP14Parser.g4に変更を加えていきます。
初期化にかかわるルールはinitializerです。C言語のルール定義を参考にして、次のように変更を加えました。

initializerList:
-	initializerClause Ellipsis? (
+	designation? initializerClause Ellipsis? (
-		Comma initializerClause Ellipsis?
+		Comma designation? initializerClause Ellipsis?
	)*;
+
+ designation
+    :   designatorList Assign
+    ;
+
+ designatorList
+    :   designator+
+    ;
+
+ designator
+    :   LeftBracket constantExpression RightBracket
+    |   Dot Identifier
+    ;

84%に上昇

前回の記事と同様の手順で、パーサーを生成し同じファイルに対して構文解析をかけた結果、パス率は84%となりました。
前回の約70%からはだいぶ向上した考えているものの、まだまだ向上の余地があるという状況です。C++14 Support in GCCを目指して次回も取り組んでいこうと思います。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?