Avance.Lab

技術紹介

第4回 MISRA-C ~ルールNo.10.3の解説~

公開日:2023.05.12 更新日:2023.05.12

tag: 組み込み車載

組み込み系のソフトウェア設計標準規格であるMISRA-Cについて、
実際にはどのようなコードが逸脱とされるのか、どんな対策案があるのか、ルールをピックアップして紹介したいと思います。

今回取り上げるルールは以下になります。

MISRA-C:2004 ルールの内容

ルールNo.10.3
カテゴリー必要
内容整数型の複合式の値は、式の潜在型と同じ符号属性をもつ、より小さな方へのキャストだけが許される。
詳細ルール10.3は、複合式に対するキャストの用途を「複合式の値を複合式の潜在型より小さい型の範囲分だけ切り出す」目的に限定している。

MISRA-C:2004では、キャストについて次の3つの用途を解説しているが、複合式に対し(1)及び(3)の用途でキャストを使用することは危険であり、MISRA-C:2004ではコレを禁止している。
(1)それ以降の算術演算を実行する際の型を変更する。
(2)値を意図的に切り捨てる。
(3)明解にする目的で、型変換を明示的に行う。

(1)は、複合式内での演算の実行時の型を変更するものと誤解されることが多い。(3)は、(1)の用途との区別がつきにくく、誤解される可能性がある。

ソフト開発では頻繁に使用される「キャスト」についてのルールです。
データ型を簡単に変えられるキャストは便利なので皆さん様々な使い方をしていると思いますが、
ここでは以下3つの用途が説明されます。

(1)それ以降の算術演算を実行する際の型を変更する。
【例】u32_tmp = (long)( u16_tmp1 + u16_tmp2 );
short型で加算を実行してlong型で代入を実行しますが、long型で加算を実行すると誤解される可能性があります。
よって、MISRA-Cでは禁止しています。

(2)値を意図的に切り捨てる
【例】u16_tmp = (short)( u32_tmp1 + u32_tmp2 );
long型をshort型に切り捨てて代入します。これはMISRA-Cでは許可されています。

(3)明解にする目的で、型変換を明示的に行う。
【例】u32_tmp = (long)( (long)u16_tmp1 + (long)u16_tmp2 );
long型で加算を実行してlong型で代入を実行しますが、加算処理が既にlong型で行われているため代入でのlong型キャストは不要です。
必要なキャストと不要なキャストが混在しており区別しづらいため、明解にするためだけのキャストをMISRA-Cでは禁止しています。

[補足] 複合式とは?

何となくイメージがついている方も多いと思いますが、「複合式」とは簡単に言うと演算子を使っている式の事です。
MISRA-Cでは「定数式」「左辺値」「関数の戻り値」以外の式が複合式であると定義しています。
定数式の型はその値によって決まり、左辺値・関数の戻り値は宣言された通りの型に決まるので、除外されています。

【例】以下は複合式です。各演算子とそのオペランドで構成される式で定数以外のオペランドを含んでいます。

  • u16_tmp1 + u16_tmp2
  • u16_tmp >> 8
  • ~u16_tmp
  • func() + 1 ※func()は関数呼び出し

【例】以下は複合式ではありません。

  • 1U + 2U
  • u16_tmp
  • *u16_tmp
  • func() ※func()は関数呼び出し

逸脱したプログラムと準拠したプログラム

ルールから逸脱したプログラムと対策して準拠させたプログラムをいくつか記述します。

◆例1:同じ、もしくは、より大きな方へのキャスト
 short型の2つの変数を加算し、long型の変数へ代入する。

long u32_tmp;
short u16_tmp1 = 0xFFFF;
short u16_tmp2 = 0x0001;

u32_tmp = (long)( u16_tmp1 + u16_tmp2 );
/* 逸脱ケース	*//* 加算はshort型で行う為オーバーフローし「0」が代入される。long型へのキャストが意図通り行われていない。 */

u32_tmp = (long)( (long)u16_tmp1 + u16_tmp2 );
/* 逸脱ケース	*//* 意図通りの値を代入できる。しかし、同じlong型へのキャストになるため逸脱と判断される。 */

u32_tmp = ( (long)u16_tmp1 + u16_tmp2 );
/* 準拠ケース	*//* long型で加算が行われる為意図通り「0x10000」が代入される。 */

u32_tmp = (long)u16_tmp1 + u16_tmp2;
/* 準拠ケース	*//* ↑と同様 */

◆例2:異なる符号属性へのキャスト
 unsigned型からsigned型もしくはsigned型からunsigned型へ代入する。

/* 符号なし←符号付きのケース */
unsigned char u8_tmp; /* (10-20)=-10=0xF6 */
signed char s8_work1=10, s8_work2=20;
signed char s8_tmp;

u8_tmp = (unsigned char)( s8_work1 - s8_work2 );
/* 逸脱ケース *//* 複合式の潜在型(signed char)をunsigned charへキャストしている。式の潜在型と同じ符号属性をもつキャストではないため逸脱となる。 */

s8_tmp = ( s8_work1 - s8_work2 );
u8_tmp = (unsigned char)s8_tmp;
/* 準拠ケース *//* 異なる符号属性へ変換する場合は、加算と代入を分けて処理する。 */

/* 符号付き←符号なしのケース */
signed long s32_tmp; /* ((5<<1)*100)=1000 */
unsigned char u8_work1=5, u8_work2=100;

s32_tmp = (long)( ( u8_work1 << 1 ) * u8_work2 );
/* 逸脱ケース *//* 複合式の潜在型(unsigned char)をsinged longへキャストしている。式の潜在型と同じ符号属性をもつキャストではないため逸脱となる。 */

s32_tmp = ( ( (long)u8_work1 * 2 ) * u8_work2 );
/* 準拠ケース *//* 複合式の潜在型がsigned longになるようキャストする。また、符号付の値をシフト演算するとMISRA-Cルール12.7"ビット単位の演算子は、潜在型が符号付きのオペランドに対して適用してはならない。"から逸脱してしまうため、乗算で処理する。 */

なお、このルールのカテゴリーは「必要」ですので、
各自のコーディングルールと相違が有ったり何かしらの理由があって逸脱させたままにしたい、という場合は
無理に対応せずに逸脱の手順を踏んでOKとすることも出来ます。

後書き

キャストについてのルールを紹介しましたが、いかがだったでしょうか?
手軽に使えてコンパイルエラーも出ないので間違った使い方をしてしまう事も多いですよね。
少なくとも私は動かしてみてから間違えてた…と思う事あります(汗)
個々ルールを決めておくかMISRA-Cのような規格を守ることで防止できますので、宜しければ活用ください。

引用・参考文献

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

y

主に組み込み開発を担当しています。

関連記事