関数パラメータとその使用方法
Basic ground rules for programming – function parameters and how they are usedのざっくりとした翻訳です。
プログラミングの基本的な基本ルール – 関数パラメータとその使用方法
すべてのシステムプログラミングに適用されるいくつかの基本的な基本ルールがあり、ほとんどのドキュメントでは説明が気にならないほど明白です。なぜなら、これらのルールは、表現する必要のないほど、その技術の実践者によって内面化されているはずだからです。運転ルートをプロットするときに、誰かの裏庭を通り抜けたり、一方通行の道を間違った方向に進んだりすることを考えないのと同じように、経験豊富なチェスプレーヤーが次に何をすべきかを決定するときに違法な動きさえ考慮しないのと同じように、経験豊富なプログラマーは、ドキュメントに明示的な許可がない限り、次の基本的なルールに違反することさえ考えません。
-
定義されていないものはすべて未定義です。これはトートロジーかもしれませんが、有用なものです。以下のルールの多くは、このルールの特殊なケースにすぎません
-
すべてのパラメーターは有効である必要があります。関数のコントラクトは、呼び出し元が条件を順守し、条件の 1 つがパラメーターが実際に主張しているとおりである場合にのみ適用されます。これは、「定義されていないものはすべて未定義である」というルールの特殊なケースです
- 特に明示的に許可されていない限り、ポインタはNULLにできません。
- ポインターは、実際に指し示そうとしているものを指しています。 関数がCRITICAL_SECTIONへのポインタを受け入れる場合は、実際には有効なCRITICAL_SECTIONへのポインタを渡す必要があります。
- ポインタが適切に位置合わせされている。ポインタのアライメントは基本的なアーキテクチャ要件ですが、アライメントエラーに非常に寛容なプロセッサアーキテクチャに甘やかされているため、多くの人が見落としていることです。
- 呼び出し元には、指し示されているメモリを使用する権利があります。これは、解放されたメモリや、呼び出し元が制御できないメモリへのポインタがないことを意味します。
- すべてのバッファは、宣言または暗黙的に指定されたサイズに対して有効です。ポインタをバッファに渡し、その長さが 10 バイトであると言った場合、バッファは実際には 10 バイトの長さである必要があります。
- ハンドルは、破棄されていない有効なオブジェクトを参照します。関数がウィンドウハンドルを必要とする場合は、実際には有効なウィンドウハンドルを渡す必要があります。
-
すべてのパラメータは安定である
- 関数呼び出しの進行中は、パラメータを変更できません。
- ポインターを渡すと、呼び出しの間、指し示されたメモリは別のスレッドによって変更されません。
- 指し示されたメモリを解放することもできません。
-
正しい数のパラメーターが、正しい呼び出し規則で渡されます。これは、「定義されていないものはすべて未定義である」というルールの別の特殊なケースです
- ありがたいことに、現代のコンパイラは間違った数のパラメータを渡すことを拒否しますが、いずれにせよ、通常は不正なキャストによって、間違った数のパラメータをこっそり通過する人の多さに驚かれることでしょう。
- オブジェクトに対してメソッドを呼び出す場合、thisパラメータはオブジェクトです。繰り返しになりますが、これは現代のコンパイラが自動的に処理するものですが、CからCOMを使用している人(はい、存在します)はthisパラメータを手動で渡す必要があり、時には失敗することがあります。
-
関数パラメータの有効期間
- 呼び出された関数は、関数の実行中にパラメーターを使用できます。
- 呼び出された関数は、関数が戻った後はパラメータを使用できません。もちろん、発信者と呼び出し先が寿命を延ばす手段に合意した場合は、それらのルールが適用されます。
- COM オブジェクトへのポインターであるパラメーターの有効期間は、メソッドを使用して延長できます。IUnknown::AddRef
- 多くの関数には、関数が戻った後に使用するという明確な意図を持つパラメーターが渡されます。その後、パラメーターの有効期間が少なくとも関数が必要とする限りであることを確認するのは、呼び出し元の責任です。たとえば、コールバック関数を登録する場合、コールバック関数は登録解除するまで有効である必要があります。
-
入力バッファー
- 関数は、呼び出し元によって提供されたバッファーの全範囲から読み取ることができます (結果を決定するためにすべてのバッファーが必要ない場合でも)。
-
出力バッファ
- 出力バッファーは、入力バッファーまたは別の出力バッファーとオーバーラップすることはできません。
- 関数は、呼び出し元によって提供されたバッファーの全範囲に書き込むことができます (結果を保持するためにすべてのバッファーが必要ない場合でも)。
- 関数が関数呼び出しの結果を保持するためにバッファーの一部のみを必要とする場合、バッファーの未使用部分の内容は未定義です。
- 関数が失敗し、ドキュメンテーションで失敗時のバッファの内容が指定されていない場合、出力バッファの内容は未定義です。これは、「定義されていないものはすべて未定義である」というルールの特殊なケースです。
- COM は出力バッファーに独自のルールを課すことに注意してください。COM では、障害発生時でもすべての出力バッファーがマーシャリング可能な状態である必要があります。重要なマーシャリングが必要なオブジェクト (インターフェイス ポインターと BSTR が最も一般的な例) の場合、これは、失敗した場合には出力ポインタが NULL でなければならないことを意味します。
(ここでのすべての発言は基本的な基本原則であり、絶対的に避けられない事実ではないことを忘れないでください。ここにあるすべての文の先頭に「反対の兆候がない場合」があると仮定します。発信者と呼び出し先がルールの例外に同意した場合は、その例外が適用されます。たとえば、ポインタが volatile として宣言されている場合、それは「この値は他のスレッドから変更される可能性がある」と明示的に示されています。そのため、関数パラメータを変更してはならないというルールは、このようなポインタには適用されません。) これを考え出すのは難しかった。ちょうど、チェスで反則手のリストを作るのが難しいのと同じように。ルールは非常に自動的なので、実際にはルールではなく、単にルールであるものであり、そうでなければ考えるだけでもクレイジーです。その結果、他にも「言う必要のないほど明白なルール」が欠けていると確信しています。(たとえば、「スレッドが他の誰かの関数内にある間は、スレッドを終了することはできません」など)。
関数呼び出しに対して何をしてよいかを判断するための便利な経験則のひとつは、「もし誰かが自分にそれをしたらどう思うか?」と自問してみることです。(これは「もしそれが可能だったらどうなるか」というテストの一種です。)