Skip to main content

組み込みシステムのための現代C++:C++20が業界の「スイートスポット」となった理由

読了時間:

10分

要点まとめ

最新のコンパイラとツールのサポートにより、組み込み開発チームが今すぐ安心して導入できるC++20の機能を紹介します。

  • C++20が最適な選択肢: コンパイラ、デバッガ、静的解析ツールがすでに対応しているため、C++20は多くの組み込み開発チームが安心して導入できる最新の規格です。
  • まずはコンパイル時の機能から: Concepts、constexpr、std::spanなどの機能は、コードサイズの増加やヒープの使用を伴わずに、安全性と可読性を高めます。
  • 段階的に導入: CやC++の旧規格で書かれたコードはC++20と併せてコンパイルできます。IAR C/C++コンパイラもC++20に対応しているため、新しいコンポーネントから導入を始められます。

C++23が公開され、C++26も間もなく公開される見込みです。 しかし、多くのファームウェア開発チームを覗いてみると、C++11で書かれたコードや、マクロが大量に組み込まれたC言語のコードが依然として見受けられます。その理由は、通常、関心がないからではありません。組み込み開発チームが言語標準を採用できるのは、それを取り巻くツールチェーン全体が整ってからでなければいけないからです。

コンパイラがそれをサポートしていなければなりません。デバッガがそれを理解できなければなりません。静的解析ツールやコーディング標準がそれを網羅していなければなりません。安全性が重要な製品の場合、認証プロセスがそれを承認しなければなりません。これらの条件のいずれかを満たさない言語機能は、たとえどれほど魅力的に見えても、採用の対象外となります。

そう考えると、組み込みチームにとって重要な問いは、「どのC++標準が最新か」ではありません。「自信を持って導入できる最新の標準はどれか」ということです。今日のほとんどのチームにとって、その答えはC++20です。

なぜ組み込みC++は最新の標準規格に遅れをとっているのか?

組み込み開発は、デスクトップやサーバー向けソフトウェアとは異なるタイムラインで進められます。製品は10年以上現場で使用され続け、ツールチェーンの変更はすべて、本番コードに反映される前に検証されなければなりません。新しい標準が広く実装され、解析ツールによってサポートされるようになる頃には、多くの組織ではまだ前の標準の大規模な導入を始めたばかりという状況です。

一方で、進化へのプレッシャーは高まっています。ファームウェアは現在、接続性、リモートアップデート、機能安全、リッチなユーザーインターフェース、場合によってはエッジAIワークロードまで処理しています。コードベースが拡大するにつれ、エンジニアリング上の最大の課題は、機能を最初に動作させることではなく、長い製品ライフサイクルを通じてコードの保守性を維持することであることが多いのです。 より強力な型安全性と明確な抽象化は、まさにその点で役立ちます。

C++20は、これら2つの要素が融合したものです。表現力と安全性の面で有意義な前進をもたらし、コンパイラやツールの間で十分に成熟する時間を経ています。

組み込み分野において、C++20のどの機能が最も重要か?

すべてのC++20機能がマイクロコントローラにおいて等しく有用というわけではありません。組み込みチームが真っ先に採用する傾向にある機能には、すべて共通点があります。それは、コンパイル時に機能し、バイナリに何も追加しないという点です。

    • コンセプトを使用すると、テンプレートが何を期待しているかを明示できるため、誤用があった場合でも、何ページにも及ぶテンプレート診断メッセージではなく、読みやすいエラーメッセージがコンパイル時に表示されます。
    • consteval および constinit は、拡張された constexpr と組み合わせることで、ルックアップテーブル、CRC テーブル、構成データをコンパイル時に計算し、起動時の初期化を必要とせずにそれらを直接フラッシュメモリに配置することを可能にします。
    • std::span は、バッファとその長さを一緒に渡すため、ポインタとサイズに関する一連のミスを排除します。
    • 指定初期化子により、レジスタや周辺機器の設定構造体が読みやすくなり、誤りを犯しにくくなります。
    • std::bit_castや bit ヘッダー内のその他の関数は、安全でない型変換や手書きのビット操作を、明確に定義された標準コードに置き換えます。

以下に、コンセプトとstd::spanが実際にどのように連携するかを示します。このロギング関数は、書き込み操作を提供するあらゆる周辺機器を受け入れます:

#include <concepts>

#include <cstddef>

#include <span>

 

template <typename T>

concept Uart = requires(T port, std::byte b) {

port.write(b);

};

 

void log(Uart auto& port, std::span<const std::byte> msg) {

for (auto b : msg) {

port.write(b);

}

}

Uart を満たさない型を渡すと、コンパイラは満たされていない要件を明示して即座にエラーを返します。span は自身の長さを保持しているため、バッファサイズが一致しない状態でこの関数を呼び出すことはできません。生成されるコードは、手作業で記述する場合と同じようなコンパクトなループになります。

C++20の機能の中には、より慎重に扱うべきものもあります。コルーチン(coroutine)は、コンパイラがそうでないことを証明できない限り、その状態をヒープ上に割り当てる可能性があります。これは、動的割り当てを禁止しているシステムでは重要な問題となります。モジュールは強力ですが、より広範なエコシステムにおけるビルドツールのサポートはまだ定着しつつある段階です。どちらも評価する価値はありますが、ほとんどのチームがまず手をつけるべきものではありません。

C++20はコードサイズを増加させたり、ヒープを使用したりするのでしょうか?

これらは、組み込みエンジニアが真っ先に尋ねる2つの質問であり、明確な回答が求められます。

コードサイズに関しては、上記の機能は実行時に何のコストもかかりません。通常懸念されるのは例外とRTTIであり、これらはアプリケーションサイズを増加させる可能性があります。IAR C/C++コンパイラでは、--no_exceptionsおよび --no_rttiオプションを使用して両方を無効にできるため、各プロジェクトで必要な機能のみを有効にすることができます。 たとえ有効にしていたとしても、実際のオーバーヘッドは、単にそれらが利用可能かどうかではなく、どのように使用されるかによって決まります。

動的メモリに関しては、使用する標準ライブラリのどの部分かによって答えが異なります:

    • 割り当てなし: std::array、std::span、std::string_view、std::optional、およびstd::variantは決してメモリを割り当てません。
    • 割り当ての可能性あり: std::vector、std::string、および std::function は割り当てを行う可能性があり、実装によっては例外処理で動的メモリが使用される場合があります。

厳格な「ヒープ禁止」ルールを設けているチームは、通常、最初のグループに限定するか、独自のアロケータを提供します。

この区別は、決定性についても同様に重要です。コンパイル時の機能は、割り込みのレイテンシやタイミングに影響を与えません。メモリを割り当てたり例外をスローしたりする可能性のあるものはすべて、適切に記述された C 言語の場合と同様に、割り込みサービスルーチンやハードリアルタイムパスからは除外すべきです。

IAR C/C++ コンパイラは何をサポートしていますか?

IAR C/C++ コンパイラは、デフォルトで C++20 ソースコードを受け入れ、ISO/IEC 14882:2020 にほぼ準拠しています。Libc++ ライブラリと組み合わせることで、C++20 の言語およびライブラリ機能の大部分を利用できます。

サポートはコンパイラだけにとどまりません。IAR C-SPY デバッガは STL コンテナを読みやすい形式で表示し、例外がスローされた場合やハンドラなしで例外がエスケープした場合に実行を停止させることができます。これにより、ターゲット上でも、最新の C++ コードを通常の C コードと同様に詳細に確認することが可能になります。

機能安全プロジェクトでC++20は使用できるか?

自動車、産業、医療市場のチームにとって、言語機能は安全プロセスに適合する場合にのみ重要となります。

安全関連のC++に関する現在のコーディング標準である「MISRA C++:2023」は、C++17に基づいています。したがって、C++20固有の構文は、その明示的なガイダンスの範囲外となり、チームはそれらをどのように扱うかを決定する必要があります。多くの場合、動作が明確で十分に理解されている機能の使用に限定することで対応します。IAR C-STATは 、コードをMISRA C++に照らしてチェックし 、チームが決定したサブセットを確実に適用できるよう支援します。

実環境での検証

本記事で使用した例を検証するため、Libc++を有効にしたIAR LLVM C/C++コンパイラを使用して、コードのコンパイル、リンク、実行を行いました。 Concepts、std::span、std::bit_cast、std::source_location、std::optional、std::variant、コンパイル時のCRCテーブル生成、constexpr、consteval、およびconstinitなどの機能は、Cortex-Mターゲット上で正常にコンパイルおよび実行されました。 また、CMakeプリセットを使用した標準的なCMakeベースのワークフローを通じて、同じアプリケーションをビルドしました。これにより、C++20を用いた最新の組み込み開発の実用的な実例を示しています。

ご自身で試してみたい方は、ソースコード、CMakeLists.txt、CMakePresets.json を含む完全なサンプルプロジェクトをこちらから入手してください。

図1. CMakeプロジェクトからインポートされた後、IAR Embedded Workbenchfor Arm上で実行されているEmbedded C++20デモ。この例では、Libc++を有効にしたIAR LLVM C/C++コンパイラを使用し、Cortex-Mターゲット上で最新のC++20機能が正常に動作している様子を示しています。

 

「ビッグバン」的な移行をせずにC++20を導入するには?

技術リーダーにとって朗報なのは、導入が「すべてか、あるいは何もかも」という二者択一である必要はないということです。既存のCおよび旧式のC++コードは、新しいC++20コードと並行してコンパイルされ続けるため、チームは既存の製品を出荷し続けながら、新しいドライバやコンポーネントから始めて、モジュールごとに新しい機能を導入することができます。

ビジネス上のメリットは時間をかけて蓄積されていきます。「concepts」、「span」、「constexpr」を使用するコードはレビューが容易で、誤用されにくいため、数十年に及ぶ製品ライフサイクル全体を通じて保守コストを削減できます。 また、人材面でもメリットがあります。この分野に参入するエンジニアはC++11以降からC++を学ぶため、彼らが慣れ親しんだC++に似たコードベースであれば、参画や貢献が容易になります。

IAR Platformはコンパイラ、デバッガ、静的解析を1つのワークフローに統合しているため、こうした改善はツールごとに順次行われるのではなく、ツールチェーン全体に一度に反映されます。

自分のコードで C++20 を試してみる

ご自身のプロジェクトで C++20 を評価する最も手っ取り早い方法は、ご自身のコードの一部を C++20 でコンパイルし、その出力を確認することです。

IAR Embedded Workbenchの無料評価版を開始するか、インタラクティブなIAR Embedded Development Platformのデモで完全なワークフローを体験してください。

 

 

 

    IAR Embedded Development Tools

    Explore now