Avance.Lab

技術紹介

第5回 MISRA-C ~ルールNo.19.4の解説~

公開日:2024.11.29 更新日:2024.11.29

tag: 組み込み車載

組み込み系のソフトウェア設計標準規格であるMISRA-Cのルールの中から、
CマクロについてMISRA-C 2004では必要・推奨ルールが幾つかありますので、ピックアップして紹介したいと思います。
今回取り上げるルールは以下になります。

MISRA-C:2004 ルールの内容

ルールNo.19.4
カテゴリー必要
内容Cマクロは、波括弧で囲まれた初期化子、定数、括弧で囲まれた式、型修飾子、記憶域クラス指定子、do-while-zero構造だけに展開されなければならない。
詳細ルール19.4は、マクロの用途(置換結果)を次の6種類に制限するものである。
定義と呼び出しの両方で、意図どおりの用途であることを確認する必要がある。
 (1)初期化子
 (2)定数
  C90 6.1.3項の定数(整数定数、文字定数、列挙定数など)、C90 6.1.4項の文字列リテラル、C90 6.1.8項の前処理数、それらに置換する用途のマクロ
  (アセンブリ言語など処理系とのインタフェースに使う低レベル処理用の数と文字表現への置換も含む)
 (3)括弧で囲まれた式(定数式を含む)
 (4)型修飾子const,volatile,const volatile,volatile constへの置換
 (5)記憶域クラス指定子extern,static,auto,registerへの置換
 (6)do-while-zero形式を使用し、1つ又はそれ以上の文を保護したマクロ

C言語のマクロに関するルールです。
ソースコードの可読性を上げる目的などで、よくマクロを使用すると思います。
マクロを使用するにあたり、ルール19.4から逸脱してしまったプログラムには以下の問題が潜んでいます。
“プログラマの意図どおりにマクロ呼び出しが行われているか、置換結果をコンパイラが問題なく処理できるかを確認することは難しい”

【補足】マクロとは?

C言語におけるマクロとは、プログラム内の文字列をあらかじめ定義されている規則にしたがって置換する機能のことをいいます。
マクロには、プログラム中の文字列を単に指定した文字列に変換するオブジェクト形式マクロと引数を用いて関数のように動作する関数形式マクロの2種類があります。

・オブジェクト形式マクロの例
“#define NUM 3″のように、”#define 文字列1 文字列2″で定義します。
オブジェクト形式マクロ定義しておくと、値を変更したくなった時に定義値だけを変更すればすべてに反映されるため、保守性が向上して修正しやすくなります。

・関数形式マクロの例
関数形式マクロとは関数のように動作するマクロで、”#define BIT_SET(a, b) (a |= 1 << b)”のように”#define マクロ名(引数) 処理”で定義します。
関数形式マクロは、関数呼び出しにかかるオーバーヘッドがないメリットがありますが、呼び出しが多いとプログラムのサイズが増加するといったデメリットがあります。

準拠したプログラム

ルールNo.19.4に準拠したプログラムを紹介します。

#define MACRO1( val ) { ( val ), 0 }
uint16 u16_tbl[2][2] = {
    MACRO1( 1 ),
    MACRO1( 2 )};
/* 波括弧で囲まれた初期化子 */

#define MACRO2 ( 3 )
/* 定数 */

#define MACRO3 ( 3 + 5 * 5 )
/* 括弧で囲まれた式 */

#define MACRO4 ( const )
/* 型修飾子への置換 */

#define MACRO5 ( extern )
/* 記憶域クラス指定子への置換 */

#define MACRO6 ()\
do {\
    DI();\
    work_gbl = work_count;\
    work_gbl = work_gbl + TIME_COUNT;\
    EI();\
} while( 0 )
/* do-while-zero形式を使用 */

ルールNo.19.4の中の”(6)do-while-zero形式を使用し、1つ又はそれ以上の文を保護したマクロ”について、仮にdo-while-zeroを使用せずに逸脱させた場合、どのような問題が発生するでしょうか?
上記の”MACRO6″から、do-while-zeroを削除した”MACRO7″を用意しました。
ある条件を満たした時、”MACRO7″を実行するようにプログラムを組んでみます。

#define MACRO7 ()\
    DI();\
    work_gbl = work_count;\
    work_gbl = work_gbl + TIME_COUNT;\
    EI()
/* do-while-zero形式を未使用 */

if ( a > b )
    MACRO7();

こちらのプログラムで展開されるコードは以下のようになります。

#define MACRO7 ()\
    DI();\
    work_gbl = work_count;\
    work_gbl = work_gbl + TIME_COUNT;\
    EI()
/* do-while-zero形式を未使用 */

if ( a > b )
    DI();
work_gbl = work_count;
work_gbl = work_gbl + TIME_COUNT;
EI();

条件を満たした時にDI()からEI()まで処理を実行したいのに、DI()のみ実行され条件の判定結果に関係なく、代入処理が実行されるように展開されました。
これはプログラマの意図とは異なる動作になります。
do-while-zero形式を使用している、”MACRO6″を使用した場合、以下のように展開され、プログラマの意図どおりの動作になります。

if ( a > b )
    MACRO6();

if ( a > b )
    do {
        DI();
        work_gbl = work_count;
        work_gbl = work_gbl + TIME_COUNT;
        EI();
    } while( 0 );

後書き

MISRA-Cのマクロルール、いかがだったでしょうか?
保守性、可読性の面からマクロを使用するプログラムも多いと思います。
定義の仕方によっては、今回紹介した動作のようにプログラマの意図しない動作に繋がる可能性もあります。
コンパイルエラーも発生しないため、マクロ使用箇所の動作確認や展開されたコードの確認は怠らないようにしましょう。
ルールに準拠していれば意図しない動作にはならないため、是非活用してみてください。

引用・参考文献

「組込み開発者におくるMISRA-C:2004 C言語利用の高信頼化ガイド」、MISRA-C 研究会編、ISBN 9784542503342、日本規格協会

TMIH

主に組み込み系を担当。食べることが趣味

関連記事