constを活用する(ROM配置と書き換え防止)
| 設計内容分類 | 組み込みソフトウェアにおけるC言語記述 |
|---|
実現したい仕様・課題
フォントデータや設定テーブル、係数表のような書き換えない大きなデータを通常の変数として持つと、限られたRAMを圧迫します。また、関数に渡したデータが関数内で意図せず書き換えられても、コンパイル時には検出できません。RAMの節約と、誤った書き換えの防止を両立したいというのが課題です。
設計のポイント
書き換えないデータにはconst修飾子を付けて宣言します。組み込み開発におけるconstの効果は大きく2つあります。1つ目はメモリ配置です。const付きで定義したグローバルなテーブル・配列は、一般的な組み込み向けコンパイラではROM(フラッシュ)領域に配置され、貴重なRAMを消費しません。フォントデータ、ルックアップテーブル、フィルタ係数、初期設定値、メッセージ文字列など、サイズの大きい固定データほどconst化の効果が大きくなります(配置の詳細は処理系・リンカ設定に依存するため、マップファイルで確認します)。2つ目はコンパイル時の書き換え検出です。関数の引数を const uint8_t *p のようにconstポインタで受ければ、「この関数はデータを読むだけで変更しない」という契約がコードに明示され、関数内で誤って書き換えるコードはコンパイルエラーとして検出されます。読み取り専用のはずのデータが壊れる、という調査困難な不具合を型システムで予防できます。#defineとの使い分けは、単純な定数値の置換は#define(またはenum)、型とアドレスを持つ固定データ(テーブル・構造体・文字列)はconst、と整理できます。なお「constを付けたRAM上のローカル変数」はROM配置にはならず書き換え防止のみの効果である点、constデータをvolatileレジスタ定義と組み合わせる場合の修飾子の位置(何を指すconstか)には注意して使い分けます。
【コード例】
// ROM(フラッシュ)に配置される固定テーブル
const uint16_t sin_table[256] = { 0, 402, 804, /* ... */ };
const char msg_start[] = "SYSTEM START\r\n";
// 「読むだけ」の契約を型で明示(書き換えはコンパイルエラー)
uint8_t calc_sum(const uint8_t *data, size_t len) {
uint8_t sum = 0;
for (size_t i = 0; i < len; i++) sum += data[i];
return sum;
}
補足情報
設計のポイント 一覧
組み込みソフトウェアにおけるC言語記述のポイント
- リングバッファで受信データを取りこぼさない
- switch-caseによるステートマシンの実装
- 割り込みハンドラは短く書く
- constを活用する(ROM配置と書き換え防止)
- ビット演算によるレジスタ・フラグ操作
- 固定幅整数型(stdint.h)を使う
- volatile修飾子を正しく使う
- 構造体の宣言にpackedを指定する
- ソースコードのエンディアン変換
- sizeofを使用する
- 特殊文字の記述にエスケープ文字を使用する
- 変数の初期化漏れを防ぐ
- 外部バスの使用
- sprintf関数を使う
- 列挙型(enum)を使う
- 構造体を使う
- 標準関数を使う
- 条件演算子を使う
- インクリメント演算子を使う
- C言語記述の際はアプリ層とドライバ層に分ける



