こんにちはI-Rです。
組み込み系のソフトウェア設計標準規格であるMISRA-Cについて、
実際にはどのようなコードが逸脱とされるのか、どのような対策案があるのか、抜粋して紹介したいと思います。
今回紹介するルールは関数の終了部分に関するルールとなります。
関連性の高い2つのルールを紹介します。
1.MISRA-C:2004 ルールNo.14.7の紹介
1.1 ルールNo.14.7について
ルールNo. | 14.7 |
カテゴリー | 必要 |
内容 | ルール14.7は、return文の記述位置を関数の最後に限定するルールである。 |
詳細 | 関数の最後とは、関数本体のブロックを終了させる波括弧の”}”を指す。 関数の出口とはreturn文を指す。 (void型関数でreturn文が省略された場合の関数の最後も同様。) 1つの関数のreturn文をその関数の最後に1つだけ記述することで、 関数内の制御の流れが単純化され、可読性・保守性が向上する。 関数の出口が複数ある場合、制御フローが関数の途中で中断されることになり、 読み手の混乱を招く危険性がある。 また、関数インターフェースの修正内容によっては、複数の出口それぞれに修正が必要になる場合があるため、 修正忘れなどを誘発する可能性がある。 |
ルール14.7は関数の出口は1箇所のみにすべきというルールです。
関数の出口を複数設置した場合、各パターンの処理を短くできますが、
複数の出口から関数の処理が中断される事で、処理の流れを把握しづらくなり、可読性が低下します。
また、処理の流れが把握しづらくなる事で処理の内容に見落としが起こる可能性も増えるので、保守性も低下します。
一方で関数の出口を1箇所のみにする事で、各パターンの処理が長くなりますが、
関数外部との繋がりが1箇所のみになり、処理全体の可読性・保守性が向上すると考えられます。
サンプルプログラムを以下に記入します。
1.2 ルール14.7を逸脱したプログラム
共通変数定義
全サンプルプログラムで使用するグローバル変数の定義を以下に記述します。
これ以降のサンプルプログラムでは下記の変数の定義の記述は省略します。
unsigned long ul_a; /*関数外部で更新されるグローバル変数を定義 */
unsigned long ul_b; /*関数外部で更新されるグローバル変数を定義 */
関数の出口が複数あるプログラムです。
void ng_func1()
{
if( ul_a == 1UL )
{
ul_b = 10UL;
return; /*関数の出口1*/
}
if( ul_a == 0UL )
{
ul_b = 0UL;
return; /*関数の出口2*/
}
return; /*関数の出口3*/
}
各if文の内部にて、グローバル変数のul_bを更新し、そのままreturn文で関数を終えています。
機能としては問題ありませんが、関数の出口毎に関数が途切れる為、可読性が低いです。
1.3 ルール14.7に準拠したプログラム
関数の出口が1箇所のみのプログラムです。
void ok_func1()
{
if( ul_a == 1UL )
{
ul_b = 10UL;
}
if( ul_a == 0UL )
{
ul_b = 0UL;
}
return; /*関数の出口*/
}
各if文の内部にて、グローバル変数のul_bを更新し、return文を関数の最後に記述して関数を終えています。
関数の出口が一つのみの為、可読性が高いです。
もし、関数戻り値を非voidへ変更することになった場合、逸脱したプログラムでは複数の出口それぞれに修正が必要となってしまいますが
準拠したプログラムであれば唯一の出口のみ修正すればよいので修正漏れは発生しづらくなります。
2.MISRA-C:2004 ルールNo.16.8の紹介
2.1 ルールNo.16.8について
ルールNo. | 16.8 |
カテゴリー | 必要 |
内容 | 戻り値の型が非voidの関数の場合、すべての出口で、式をもつ明示的なreturn文を記述しなければならない。 |
詳細 | 制御が到達しないようにreturn文を記述することと、そのreturn文には式をもたせることを要求している。 関数を終了させる波括弧”}”に制御が到達することは、式をもたないretun文を実行することと等価である。 returnに記述する式の潜在型が関数の型と異なる場合に明示的なキャストを記述する必要がある。 ルール16.8では「すべての出口で」となっているが、関数が複数の出口を持つことは、 ルール14.7に非適合であるため、実際には「最後の唯一の出口で」式をもつ明示的なreturn文を記述する必要がある。 |
ルール16.8はvoid型以外のデータ型関数の出口には戻り値を記述するというルールです。
上記のルールを違反した場合、関数の戻り値が正しく出力されずバグの原因となります。
関数戻り値が抜けている場合、コンパイラにもよりますが、エラー扱いとならずに
コンパイルが完了してソフトにバグが発生してしまうため、注意する必要があります。
2.2 ルール16.8を逸脱したプログラム
関数の出口に戻り値がないプログラムです。
unsigned long ng_func2()
{
unsigned long ret; /*関数戻り値のローカル変数を定義*/
ret = 0UL;
if( ul_a == 1UL )
{
ret = 10UL;
}
else if( ul_a == 0UL )
{
ret = 5UL;
}
else
{
/*何もしない*/
}
return; /*関数の出口に戻り値がない*/
}
ローカル変数であるretを関数内部で更新していますが、関数戻り値として記述していないため、retの値が正しく出力されません。
そのため、関数の呼び出し元で仕様通りの結果を受け取れずにバグとなる可能性があります。
2.3 ルール16.8に準拠したプログラム
関数の出口に戻り値の変数を設定しているプログラムです。
unsigned long ok_func2()
{
unsigned long ret; /*関数戻り値のローカル変数を定義*/
ret = 0UL;
if( ul_a == 1UL )
{
ret = 10UL;
}
else if( ul_a == 0UL )
{
ret = 5UL;
}
else
{
/*何もしない*/
}
return ret; /*関数の出口に戻り値の変数を設定*/
}
ローカル変数であるretを関数内部で更新後に、関数戻り値に設定しているため、retの値が正しく出力されます。
コンパイラがエラー扱いしない場合は見落としやすくなりますし、問題が起こっても原因を特定しづらい箇所と思われます。
静的解析ツール等で本ルールに準拠しているか確認して見落としのないようにした方が良いと考えています。
3.ルール14.7&16.8を準拠したプログラム、逸脱したプログラム
3.1 ルール14.7&16.8を逸脱したプログラム
関数の出口が複数あり、戻り値の設定が正しく行われていない関数の出口が存在するプログラムです。
unsigned short ng_func3()
{
unsigned long ret; /*関数戻り値のローカル変数を定義*/
ret = 0UL;
if( ul_a == 1UL )
{
ret = 10UL;
return; /*関数の出口1*/
}
else if( ul_a == 0UL )
{
ret = 5UL;
return ret; /*関数の出口2に戻り値の型と異なる型の変数を設定*/
}
else
{
/*何もしない*/
}
return (unsigned short)ret; /*関数の出口3に戻り値の型と同じ型の変数を設定*/
}
ローカル変数であるretを各if文の内部にて更新し、関数戻り値に設定しています。
関数の出口が3箇所あり(ルール14.7逸脱)、
出口1では戻り値の変数が抜けており(ルール16.8逸脱)、
出口2では関数のデータ型と異なる変数を戻り値に設定しております(ルール16.8逸脱)。
3.2 ルール14.7&16.8に準拠したプログラム
関数の出口が1箇所のみであり、戻り値の設定が正しく行われているプログラムです。
unsigned short ok_func3()
{
unsigned long ret; /*関数戻り値のローカル変数を定義*/
ret = 0UL;
if( ul_a == 1UL )
{
ret = 10UL;
}
else if( ul_a == 0UL )
{
ret = 5UL;
}
else
{
/*何もしない*/
}
return (unsigned short)ret; /*関数の出口*/
}
ローカル変数であるretを各if文の内部にて更新し、関数の出口の戻り値に設定しています。
出口が一箇所のみとなったため、関数の可読性が高く、戻り値も正しく出力されます。
4.後書き
今回は関数の出口のルールについて紹介しました。
関数の出口は非常に重要な機能であり、異常があれば関数自体が機能不全となってしまいます。
今回紹介したルールに準拠する事で可読性が向上し、関数のバグの発生を未然に防ぐことにつながると思います。
本記事が関数作成時の参考になれば幸いです。
過去に紹介したMISRA-Cルールの記事のリンク一覧です。よろしければこちらもご覧下さい。
第1回:https://avancesys.co.jp/laboratory/article/%e7%ac%ac1%e5%9b%9e-misra-c-%ef%bd%9emisra-c%e8%a6%8f%e6%a0%bc%ef%bd%9e/
第2回:https://avancesys.co.jp/laboratory/article/%e7%ac%ac2%e5%9b%9e-misra-c-%ef%bd%9e%e5%bf%85%e8%a6%81%e3%83%ab%e3%83%bc%e3%83%ab%e3%81%ae%e9%80%b8%e8%84%b1%e6%89%8b%e9%a0%86%ef%bd%9e/
第3回:https://avancesys.co.jp/laboratory/article/%e7%ac%ac3%e5%9b%9e-misra-c-%ef%bd%9e%e6%8e%a8%e5%a5%a8%e3%83%ab%e3%83%bc%e3%83%ab%ef%bd%9e/
第4回:https://avancesys.co.jp/laboratory/article/%e7%ac%ac4%e5%9b%9e-misra-c-%ef%bd%9e%e3%83%ab%e3%83%bc%e3%83%abno-10-3%e3%81%ae%e8%a7%a3%e8%aa%ac%ef%bd%9e/
第5回:https://avancesys.co.jp/laboratory/article/%e7%ac%ac5%e5%9b%9e-misra-c-%ef%bd%9e%e3%83%ab%e3%83%bc%e3%83%abno-19-4%e3%81%ae%e8%a7%a3%e8%aa%ac%ef%bd%9e/
5.引用・参考文献
「組込み開発者におくるMISRA-C:2004 C言語利用の高信頼化ガイド」、MISRA-C 研究会編、ISBN 9784542503342、日本規格協会
関連記事
-
第1回 Visual C++で作成したDLL内のクラスをC#で利用する方法
こんにちは、ILCです。 Visual C++ (以下 VC++)で作成されたDynamic...
公開日:2024.01.19 更新日:2024.01.19
tag : Windows
-
-
-
第1回 ラズパイを使用したBLE通信 ~ ディスプレイ、キーボード、マウスを接続しないで設定 前編 ~
こんにちは、GTです。よろしくお願いします。 最近業務でラズパイのBluetooth機能を使...
公開日:2021.12.24 更新日:2021.12.24
tag : Bluetooth Raspberry Pi
-
【新機能探訪】Android 13から導入された『アプリごとの言語設定』
こんにちは、KNSKです。よろしくお願いします。 今回は Android13の新機能である『...
公開日:2022.12.09 更新日:2022.12.09
tag : スマートデバイス
-
こんにちは。WwWです。 システム系の開発をしていると様々な問題が起こります。 そこで今回は...
公開日:2023.04.28 更新日:2023.04.28