17

今週、ここで興味深い問題に遭遇しました。

16 ビットのデータ アドレスと 32 ビットのコード アドレスを持つハーバード アーキテクチャの組み込みプラットフォームで C を使用しています。

この問題は、関数ポインターを操作しているときに発生します。次のようなコードがある場合

if (fp) fp();

また

if (fp != 0) fp();

すべて順調。

ただし、次のようなコードがある場合

if (fp != NULL) fp();

次に、NULLが として定義されているため(void *) 0、コンパイラ (この場合は gcc) は a) 警告を出さず、b) 32 ビット比較ではなく、関数ポインタに対して 16 ビット比較を行います。関数ポインターがたまたま 64k の境界上にない限り、下位 16 ビットはすべて 0 です。

現時点では、NULL に対する明示的なチェックを含む大量のコードがあります。それらのほとんどはデータ ポインターになりますが、一部は関数ポインターになります。3000 を超える結果を簡単に grep し!= NULLたり、明らかにしたりしましたが、その多くは手動で確認する必要がありました。== NULL

したがって、今私たちが望むのは次のいずれかです

  1. 関数ポインタ (データ ポインタではない) が比較されるすべてのケースを見つける方法 (代わりに、32 ビット 0 として定義する FP_NULL と比較することができます)、または

  2. 正しいことを行うように NULL を再定義します。

  3. (または、gcc ポートを更新して、このケースを検出して正しく処理すると思います)。

1で機能するアプローチは考えられません。2で考えられる唯一のアプローチは、NULLを0関数ポインターとして再定義することです。これは、データポインターに対する比較の大部分にとって非常に無駄になります。(32 ビットの比較は 4 命令、16 ビットの比較は 1 命令です)。

何か考えや提案はありますか?

4

6 に答える 6

19

最も簡単な方法は、すべての出現箇所をNULLbyに置き換えることです0。これは、関数ポインター(あなたが言うように)とオブジェクトポインターで機能します。

これは (2) NULL を plain に再定義するの変形です0

しかし、関数ポインターを比較できないという事実NULLは、実装のバグです。C99 では、ヌル ポインター定数の比較はオブジェクト ポインターと関数ポインターの両方で可能であり、NULL はこの定数に展開する必要があると述べています。

C-FAQ の質問5.8からの小さな追加:

Q: 関数へのポインタに NULL は有効ですか?
A: はい (ただし、質問4.13を参照してください)

関数ポインタの混合(void *) 0

(R..のコメントへの返信)。関数ポインタと(void *) 0一緒に使用することは明確に定義されていると思います。私の推論では、C99 ドラフト 1256 のセクションを参照しますが、読みやすいように大きな部分は引用しません。C89にも適用できるはずです。

  • 6.3.2.3 (3) は、整数定数式とそのような式をnull ポインター定数として0キャストすることを定義します。また、「ヌル ポインター定数がポインター型に変換された場合、ヌル ポインターと呼ばれる結果のポインターは、任意のオブジェクトまたは関数へのポインターと等しくないことが保証されます。」(void *)
  • 6.8.9 では、(特に) ポインター オペランドと null ポインター定数の==andオペランドが定義されています。!=これらについては、「1 つのオペランドがポインターで、もう 1 つのオペランドが null ポインター定数である場合、null ポインター定数はポインターの型に変換されます。」

結論: ではfp == (void *) 0、null ポインター定数は の型に変換されますfp。この null ポインターは、関数を指している場合と比較できfp、等しくないことが保証されます。fp代入 ( =) にも同様の節があるため、fp = (void *) 0;C も明確に定義されています。

于 2010-10-08T10:25:02.037 に答える
4

あなたが説明する方法はうまくいくはずです:

6.3.2.3/3 値が 0 の整数定数式、または型 void * にキャストされたそのような式は、NULL ポインター定数と呼ばれます。null ポインター定数がポインター型に変換された場合、null ポインターと呼ばれる結果のポインターは、任意のオブジェクトまたは関数へのポインターと等しくないことが保証されます。

そのため、NULL が再定義されていないもの(0または(void*)0同等のもの) に再定義されているか、コンパイラが準拠していません。

0すべてのファイルのすべての #includes の後に NULL を自分で (プレーンに) 再定義してみてください:-)

念のため:gcc -E問題のファイルで (前処理されたソースを出力するために) 試して、NULL の展開を確認してください。

于 2010-10-08T10:33:41.000 に答える
3

セグメント ハック (実際にはハックのみ) を試すことができるので、危険を冒さずに高速な 16 ビット比較を使用できます。各 n*0x10000 境界でサイズ 4 (またはそれ以下) のセグメントを作成するため、実際の関数が存在することはありません。

これが良い解決策であるか本当に悪い解決策であるかは、組み込みデバイスのメモリ空間によって異なります。変更されることのない 1MB の通常の Flash があれば、動作する可能性があります。64MBのNand Flashがあれば大変です。

于 2010-10-08T11:11:41.953 に答える
3

これを試すことができます:

#ifdef NULL
  #undef NULL
#endif
#define NULL 0
于 2010-10-08T10:31:45.093 に答える
3

以下にいくつかの提案を示します。

  1. 一時的に NULL を(char*)0関数ポインターに暗黙的に変換できないものに変更します。これにより、一致しないポインターとのすべての比較について警告が表示されます。次に、grep などのツールを使用して生成されたコンパイラ出力を実行し、(*)(.

  2. NULL を0(void* へのキャストなしで) として再定義します。これは NULL の別の有効な定義であり、正しいことを行う可能性がありますが、保証はありません。

于 2010-10-08T10:36:28.657 に答える
2

実装のシステムヘッダーを編集して、

#define NULL ((void *)0)

#define NULL 0

次に、ベンダーにバグレポートを提出します。ベンダーのコンパイラのバグのために、(醜いスタイルではありますが、完全に正しい)コードを変更する必要はありません。

于 2010-10-08T15:10:15.507 に答える