コードに副作用をマークするにはどうすればよいですか?
-
09-09-2019 - |
質問
私は、すべてのバイトが重要な 8051 のプロジェクトに取り組んでいます。そのため、通常は使用しないいくつかのグローバル変数を使用しています。ポインタを関数に渡す通常の方法では、ここで過剰なオーバーヘッドが追加されます。
単一ビット変数 (コンパイラ固有の拡張機能) を使用する関数が多数あります。 C) 通常の戻り値に加えて関数の結果を通知します。
bit global_error_flag = 0;
bit global_data_ready_flag = 0;
unsigned char A_Function (void) {
// Do some stuff
if ( badness ) {
global_error_flag = 0;
global_data_ready_flag = 1;
return 0;
}
if ( data_is_ready_use ) {
global_data_ready_flag = 1;
}
return a_value;
}
void Other_Function (void) {
unsigned char c;
c = A_Function();
if( global_error_flag) {
// Do error stuff here.
}
else
if( global_data_ready_flag ) {
// Do data processing here.
}
global_error_flag = 0;
global_data_ready_flag = 0;
}
この手法が悪であることを考えると、コードをより明確にする方法はあるでしょうか?
これらのフラグを設定するとどの関数呼び出しに副作用が生じるかをどのように示すのが最善でしょうか?コメントは十分ですか?API (準ハンガリー風) を示すために関数に名前を付ける必要がありますか?このような呼び出しをマークするにはマクロを使用する必要があります:
#define FUNCTION_SETS_FLAGS(code) (code)
FUNCTION_SETS_FLAGS( c = A_Function() );
他に何かアイデアはありますか?
解決
明確にするためにグローバルにラベルが付けられています。これは良いスタートです。
理想的には、間違った場合にコンパイルできないものが必要です。つまり、マクロとコメントは機能しません。
私は関数の命名規則に固執します。必ずしもハンガリー語である必要はありませんが、次のようなものです。 A_Function_Returns_Flags
, 、または、それを考えることができれば、もっと冗長になります。
他のヒント
これを「ハンガリー風」と呼ぶかどうかに関係なく、慣例を使用するのが、これを率直に示す最良の方法だと私は思います。文体的には、少なくとも私にとっては、空の #define よりも、ある種の名前付けプレフィックスの方が好ましいでしょう。
これは実際にはかなり一般的だと思います。S60 プログラミング環境では、たとえば例外をスローすることを示すために、関数上で多くの従来のタグが使用されていることは知っています。
私は博士号を取得しました。Java における同様の問題について。やってはいけないことが一つだけ言えます。ドキュメントに依存しないでください。ドキュメントを実際に読んでいる人に依存することになるからです。副作用について学ぶためにユーザーがドキュメントを読む必要があることを示すために、メソッド名にヒントを追加する必要があります。何かを選択し、それに一貫性があれば、おそらく最もチャンスが得られます。
関数がグローバル変数に影響を与えることだけを言いたい場合は、単純な (ハンガリー語) 接頭辞が役に立つかもしれません。
ただし、影響するフラグをすべて言及したい場合は、おそらく関数ヘッダーを使用するのが良いでしょう。たとえば、
/*************************************************************************
* FUNCTION : <function_name>
* DESCRIPTION : <function description>
* PARAMETERS :
* Param1 - <Parameter-1 explanation>
* Param2 - <Parameter-2 explanation>
* Param3 - <Parameter-3 explanation>
* RETURN : <Return value and type>
* GLOBAL VARIABLES USED:
* Global1 - <Global-1 explanation>
* Global2 - <Global-2 explanation>
* Global3 - <Global-3 explanation>
*************************************************************************/
これはあまり役に立ちませんが、GCC には次のような方法があります。 反対 あなたが望むもの:を持つ関数をマークするには いいえ 副作用。を参照してください。 const
そして pure
属性。これはドキュメント化というよりも最適化のためであると考えられます。コンパイラーは、指定された関数が引数以外のデータを検査しないことを知っている場合、次のようなよりスマートな最適化を実行できます。 ループ不変コードの動作.
マクロを使用して関数をシミュレートし、より多くのパラメータを持たせることができます。
unsigned char _a_function(void);
#define A_Function(ret_val) (*(ret_val) = _a_function(), !global_error_flag)
...
unsigned char var;
/* call the function */
if (!A_Function(&var))
{
/* error! */
}
else
{
/* use var */
var++;
}
コンパイルしてみたことがないので、うまくいくとは言えませんが、うまくいくはずだと思います。
まず、これらのフラグごとにプロデューサーとコンシューマが 1 つだけ存在するようにコーディングしてみます。次に、必要な場合にのみフラグをクリア/セットします。副作用を示すには、関数の先頭にある標準ヘッダー (doxygen スタイル) で十分です。
// Function func
// Does something
// Consumes ready_flag and sets error_flag on error.
int func()
{
if (ready_flag)
{
//do something then clear the flag
if (some_error)
error_flag = x;
ready_flag = 0;
}
//don't mess with the flags outside of their 'scope'
return 0;
}
一方、エラー フラグと準備完了フラグが相互に排他的である場合は、バイト (またはバイト/レジスタ内のビット) を使用して準備完了またはエラー状態を示すことができます。
0 はエラー、1 は準備完了/エラーなし、2 は準備完了/エラーなし (または -1、0、1 など)
IIRC、標準 8051 命令セットは単一ビットで動作しないため、(さまざまな) フラグにバイト全体を使用しても、パフォーマンスに大きな影響を与えることはありません。
まだチェックしていない場合は、以下もチェックしてみてください。 SourceForge 上の sdcc プロジェクト, これは、8051 もターゲットとする組み込み開発に使用することを特に意図した C コンパイラーです。さらに、このコンパイラーは、さまざまなユースケース向けに多数のカスタム、ターゲット固有、および非標準のコンパイラー組み込み関数をサポートしています。また、私が個人的に見つけた開発チームは、新しい機能強化のアイデアやその他の関連機能のリクエストに対して非常にオープンで対応する必要があります。
これらのグローバル変数をどうしても使用する必要がある場合は、関数の引数としてグローバル変数への参照を期待することで、関数がグローバル変数を変更する可能性があることを明らかにすることができます。
unsigned char A_Function (bit *p_error_flag, bit *p_data_ready_flag)
{
...
}