概要
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
オプションを使用します。
詳細は省略しますが、
- コンパイルに必要なdefineの調査と指定
- 不足しているinclude pathの調査と指定
- 厳格な型の不適合を指摘するオプションの無効化
などの細かい作業によってコンパイルできる状況までエラーを抑え込んだ結果、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++のオプションをいくつか変更して、コンパイルの可否についてみてみます。
事前に検討したところから対象のオプションは、std
とpedantic-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
を目指して次回も取り組んでいこうと思います。