web-dev-qa-db-ja.com

CおよびC ++で関数ポインターを確実にNULLに設定できますか?

P.J. Plaugerの本(The Standard C Library)で、関数ポインタをNULLに割り当てることについて警告しています。

具体的には、彼はこれを言います:

マクロ[〜#〜] null [〜#〜]は、ほぼ普遍的なnullポインター定数として機能します。これは、プログラムで宣言(または割り当て)されたデータオブジェクトがないことを示すデータオブジェクトポインターの値として使用します。 216ページで述べたように、マクロには、0、0L、または(void *)0の定義があります。

最後の定義は、任意のデータオブジェクトポインターと互換性があります。 ではありませんが、関数ポインタと互換性があります。つまり、あなたは書くことができません

int (*pfun) (void) = NULL; /* WRONG */

トランスレータは、式のタイプが初期化するデータオブジェクトと互換性がないと文句を言う場合があります。

彼は続けてこう言います:

...しかし、voidへのポインタが他の(文字以外の)ポインタと同じ表現であるという保証はありません。関数ポインタとの割り当て互換性もありません。 つまり、NULLを汎用のnullポインター定数として書き込むことはできません。また、任意のデータオブジェクトポインタの代わりに引数式として安全に使用することもできません。

私はかなり前からNULLに関数ポインタを問題なく割り当ててきましたが、移植性がないのではないかと思っています。

具体的には:

void (*test)() = NULL => gccとg ++の両方で正常にコンパイルされます

void (*test)() = 0 => gccとg ++の両方で正常にコンパイルされます

void (*test)() = (void*)0 => gccとg ++の両方で無効な変換エラーが発生しました

編集:void (*test)() = (void*)0 gccで正常にコンパイルされます。拡張子が.cppのファイルを使用していました...それでも、PlaugerがNULLは間違っていますか?

私が理解していない部分は、私のstddef.hでのNULLの定義です。

#if defined (_STDDEF_H) || defined (__need_NULL)
#undef NULL     /* in case <stdio.h> has defined it. */
#ifdef __GNUG__
#define NULL __null
#else   /* G++ */
#ifndef __cplusplus
#define NULL ((void *)0) // this line confuses me
#else   /* C++ */
#define NULL 0
#endif  /* C++ */
#endif  /* G++ */
#endif  /* NULL not defined and <stddef.h> or need NULL.  */
#undef  __need_NULL

これは、C++ではNULLを0に、Cでは((void *)0)に定義しています。それは本当にですか、それとも__nullとして定義されていますか?

編集:その場合、Plaugerによると、(void *)0への割り当てが「間違っている」にもかかわらず、NULLへの割り当てが常に機能するのはなぜですか?

編集2:C89に興味がある

17
rationalcoder
 int (*pfun) (void) = NULL;  

実際に有効です。

Cの割り当て規則は次のように述べています。

(これは初期化ですが、単純な割り当てと同じタイプの制約と変換が適用されます。)

(C99、6.5.16.1単純な割り当てp1制約)「次のいずれかが保持されます:[...] —左のオペランドはポインターであり、右はヌルポインター定数です。」

そして

(C99、7.17p3)「マクロはNULLであり、実装定義のnullポインター定数に展開されます。」

したがって、任意のポインタ(オブジェクトポインタ、関数ポインタ、またはvoid *)にnullポインタ定数を割り当てることは、Cによって許可されています。 C89。

8
ouah

void (*test)() = (void*)0 => gccとg ++の両方で無効な変換エラーが発生しました

GCCは、ファイル拡張子に基づいて言語を検出します。 GCCで.ccファイルをコンパイルすると、C++コンパイラが起動されますnot Cコンパイラ。

Cソースファイルで試してみてください。これが受け入れられることがわかります。当然のことながら、標準で許可されています。

もしそうなら、なぜNULLへの代入は常に機能しますが、(void *)0への代入は機能しないのですか?

NULLは、C++モードでは((void*)0)として定義できません。 C++モードでは、値0の整数定数、またはnullptrとして定義する必要があります。どちらも任意の関数ポインタ型に変換できます。

CおよびC++で関数ポインタを常に確実にNULLに設定できますか?

はい。準拠するCまたはC++の実装では、これは機能します。

9
user743382