組み込みC++入門

組み込み分野でも、C++の採用は一般的になりました。一方で、積極的に使う機能と慎重に検討すべき機能の判断基準は、案件ごとに異なります。この記事ではC++採用の背景、使う機能や慎重に扱う機能、標準ライブラリの取捨選択、MISRA C++・SEI CERT C++ガイドラインの位置づけ、移行実務という観点から、判断基準を整理します。

組み込みシステムでC++が使われる背景

C言語で十分だった領域でも、近年C++を採用するケースが増えています。なぜでしょうか。この問いには、組み込み分野を取り巻くハードウェアとエコシステムの両面から答えるのが自然です。

マイコン高性能化とC++対応SDKの広がり

本記事では、マイコン内蔵の不揮発性メモリをフラッシュ(ROM)、作業用の揮発性メモリをRAM、最悪実行時間をWCET(Worst-Case Execution Time)と表記します。

高性能なマイコンが安価に手に入るようになり、プログラム格納用フラッシュ(ROM)やRAMの制約も一世代前より緩みました。2024年にEclipse Foundationが公開した「IoT and Embedded Developer Survey Report 2024」では、リソースの制約が大きい機器の開発言語としてC++をあげた回答が36%で、C(55%)に次ぐ2番目でした。オープンソースRTOSのSDKでも、C++を有効にするビルド設定と使える言語機能が公式ドキュメントに明記されています。

C++規格の版数推移と組み込み関連機能

C++規格はC++11でスマートポインタ(unique_ptr・shared_ptr・weak_ptr)が導入され、C++17で構造化束縛が追加されました。constexprはC++11以降段階的に拡張され、特にC++20で適用範囲が大きく広がりました。C++20ではconstevalも導入されました。

執筆時点(2026年8月)の最新版はISO/IEC 14882:2024(通称C++23。ISO文書の発行年表記が2024になっています)です。最新情報はISO公式サイトをご確認ください。

組み込みで恩恵が大きいのは、次の3点です。

  • 実行時のコストがないconstexprとconsteval
  • テンプレートによる型安全な実装
  • RAII(Resource Acquisition Is Initialization)によるリソース管理の自動化

RAII・名前空間・テンプレートの活用

RAII・名前空間・テンプレートは、活用しやすいC++の機能です。

RAIIによるリソース管理の自動化

C++ Core GuidelinesのResource ManagementセクションRで示されるとおり、排他制御ハンドル、GPIO、DMAチャネルといった資源をコンストラクタで確保しデストラクタで解放する設計が推奨されます。リソース管理が自動化されるため、goto cleanupによる後始末の記述が不要になる、または大幅に簡素化されるケースが多いです。

名前空間による衝突回避

「名前空間」とは、識別子(型、関数、変数)やコードを論理的にまとめる領域です。大規模な組み込みシステムでは同一の識別子を複数箇所で宣言しがちですが、名前空間が異なれば区別でき、名前の衝突を回避できます。C言語で用いていたプレフィックス命名(HAL_、BSP_など)に頼らずに済みます。

テンプレートによる型安全な実装

C++ Core Guidelines TemplatesセクションTでも扱われるとおり、型安全なリングバッファなど、Cではvoid*で妥協していた抽象をテンプレートで書き直せます。実行時オーバーヘッドはなく、型ミスマッチはコンパイル時に弾かれます。

例外・動的メモリ・RTTIの扱い方

組み込みC++で慎重に扱うべき3機能のコストと代替手段を示した表。例外は、例外テーブルと巻き戻しコードがROMに乗りWCET(最悪実行時間)の見積もりが難しくなるため、無効化して戻り値でエラーを扱う。動的メモリは、長時間動作でヒープ断片化が進み確保時間がばらつくため、多相アロケータ・独自プール・placement newで置き換える。RTTIは、型情報のメタデータがROMを消費するため、無効化して静的ポリモーフィズムか仮想関数で置き換える。

判断はリアルタイム要件とROM/RAM制約から決める

一方で、慎重に扱うべき機能もあります。ここがC++を用いるかどうかの分岐点です。

例外機構のROMコストと-fno-exceptions

例外処理は問題発生時の情報を得る手段の一つです。一方で有効にすると、例外処理用のテーブルや巻き戻し(unwinding)コードが追加され、ROM使用量が増えます。またWCETの見積もりが難しくなるおそれがあります。ただし、GCC/Clangでは-fno-exceptionsにより例外機構を無効化できます。例外を使わない場合は、戻り値・エラーコード・expected相当(プロジェクト方針)など、例外以外のエラー伝播手段を明確に設計します。

動的メモリのヒープ断片化と静的アロケーター

new/deleteで異なるサイズを頻繁に確保・解放すると、長時間の動作でヒープの断片化が進みやすくなります。確保時間もばらつき、リアルタイム性が崩れるおそれもあります。std::pmrの多相アロケーター、独自プール、placement newなどの選択肢で確保パターンを制御します。

RTTIのコード肥大と-fno-rtti

実行時型情報(RTTI)を扱うdynamic_castやtypeidが有効だと、型情報のメタデータがROMに載り、コードの肥大化につながります。ISO/IEC TR 18015では、RTTIはクラス当たりおよそ数十バイト規模になり得る旨が述べられています(実際のサイズは実装・ABIに依存)。

コンパイラのオプション-fno-rttiは、この型情報の生成を止める設定です。-fno-rttiを指定しても仮想関数は動作するため、基底クラスの仮想関数で書ける処理はそのまま使えます。実行時に型を調べて分岐していた箇所は、派生クラスをテンプレート引数で渡し、コンパイル時に呼び先を決めるCRTP(Curiously Recurring Template Pattern)へ置き換えます。

標準ライブラリ(STL)の取捨選択

標準ライブラリ(STL)は目的に合った機能を選びましょう。動的確保を要するコンテナを避け、オーバーヘッドの小さい機能を選ぶのは一つの方法です。

動的確保を伴うコンテナの回避

std::vectorやstd::string、std::mapは多くの実装で動的確保を伴うため、ヒープを許さない設計には適さない場合が多いです。固定サイズが決まる場合はstd::arrayや自作リングコンテナで代替します。

std::array・std::span・std::optionalの活用

std::arrayは固定サイズの配列をカプセル化するコンテナで、at()を使えば範囲チェック付きのアクセスにも対応します。std::spanは所有権を持たない配列ビュー(C++20以降)、std::optionalは値の有無を型で表現するクラステンプレート(C++17以降)です。いずれも軽量で、設計の明確化に役立ちます。

コンパイル時計算とconstexprの活用

C++の中で組み込みシステムと相性が良いのは、実行時のコストがゼロの機能群です。constexprはその筆頭となります。

constexpr変数・関数の使いどころ

ルックアップテーブル、CRC多項式表、レジスタ設定値をconstexprで書けば、コンパイル時にROMへ確定できます。C++20のconstevalを使えば、実行時呼び出しを許さない関数としての定義が容易です。

static_assertによる仕様検証

static_assertを使えば、構造体サイズ、列挙値の範囲、アライメントの想定をコンパイル時に検証できます。設計意図をコードに明示でき、リファクタリング時の意図しない変更検出にも効果的です。

MISRA C++とベンダーガイドラインの位置づけ

C++にも、C言語のMISRA Cに相当するガイドラインが整備されています。版数と対象規格を確認した上で、できるかぎり新しいものを用いることをお勧めします。

MISRA C++:2008とMISRA C++:2023の役割

MISRA C++:2008はC++03、MISRA C++:2023はC++17を対象とするコーディングガイドラインです。安全クリティカル分野では、対象コンパイラと規格版にあわせて選ぶ形です。新しい版は、より新しい規格の機能を前提としたルール構成になっています。

SEI CERT C++とベンダー公式サンプル

SEI CERT C++ Coding Standardは、安全なコーディングに必要なルールを示すセキュリティ観点のガイドラインです。MISRAとは補完関係にあります。ベンダー推奨のガイドラインと整合を取っておくと、プロジェクトで採用されやすくなります。

C++移行の実務パターン

では、実務で移行を進めるとき、どこから手を付ければよいのでしょうか。

extern "C"によるC資産温存

既存のCライブラリはextern "C"のブロックで囲み、C++上位から呼び出す構造にします。名前マングリング(name mangling)の差を吸収する仕組みで、C言語の資産を再利用しつつ、CとC++の違いに起因するリンクエラーの多くを解決できます。

段階的導入とビルド設定

統合開発環境やビルド設定でCとC++を切り換えられる製品もあります。オープンソースRTOSでも、設定ファイルでC++を有効にする手順が案内されています。

まずはアプリケーション層だけをC++で書き直します。C-with-Classesと呼ばれる範囲から始めるのが安全です。C-with-Classesとは、C++の原型となった1979年の言語の呼び名です。クラスとコンストラクタ・デストラクタは含みますが、テンプレート・例外・RTTIは含まないことに注意が必要です。RAIIやconstexprの活用も検討する一方で、例外・動的メモリ・RTTIは案件のリアルタイム要件に応じて導入の可否を決めます。

C++は「すべての機能を導入する」「すべての機能を使わない」の二者択一ではなく、段階的に適用範囲を広げるという選択肢もあります。

組み込みソフトの世界 トップへ戻る