%p
変換指定子を使用してnullポインターを出力するのは未定義の動作ですか?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
質問はC標準に適用され、C実装には適用されません。
%p
変換指定子を使用してnullポインターを出力するのは未定義の動作ですか?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
質問はC標準に適用され、C実装には適用されません。
回答:
これは、私たちが英語の制限と標準の一貫性のない構造の影響を受ける奇妙なコーナーケースの1つです。だから、せいぜい、それを証明することは不可能なので、私は説得力のある反論をすることができます:) 1
問題のコードは、明確に定義された動作を示しています。
[7.1.4]は質問の根拠がある、のが起動してみましょう:
以下の各説明は、以下の詳細な説明で特に明記されていない限り適用されます。関数への引数に無効な値(関数のドメイン外の値やプログラムのアドレス空間外のポインターなど)がある場合、またはnullポインタ、[...その他の例...])[...]動作は未定義です。[...その他のステートメント...]
これは不器用な言葉です。1つの解釈は、リスト内の項目は、個々の説明で上書きされない限り、すべてのライブラリー関数のUBであるというものです。しかし、リストは「など」で始まり、それが例示であって網羅的ではないことを示しています。たとえば、文字列の正しいnull終了については触れていません(egの動作にとって重要ですstrcpy
)。
したがって、7.1.4の意図/範囲が「無効な値」がUBにつながるということは明らかです(特に明記されていない限り)。どの関数が「無効な値」としてカウントされるかを判断するには、各関数の説明を調べる必要があります。
strcpy
[7.21.2.3]これだけ言っています:
この
strcpy
関数は、が指す文字列s2
(終端のnull文字を含む)をが指す配列にコピーしますs1
。重複するオブジェクト間でコピーが行われた場合の動作は未定義です。
ヌルポインターについては明示的に触れていませんが、ヌルターミネーターについても触れていません。代わりに、「が指す文字列s2
」から、有効な値は文字列(つまりnullで終了する文字配列へのポインタ)だけであると推測します。
実際、このパターンは個々の説明全体で見ることができます。その他の例:
[7.6.4.1(fenv)]が指すオブジェクトに現在の浮動小数点環境を保存する
envp
[7.12.6.4(frexp)] int型の整数を格納するオブジェクトを指摘することにより
exp
[7.19.5.1(FCLOSE)] が指すストリームによって
stream
printf
[7.19.6.1]はこれについて%p
次のように述べています:
p
-引数はへのポインタvoid
です。ポインターの値は、実装定義の方法で、印刷文字のシーケンスに変換されます。
Nullは有効なポインター値であり、このセクションでは、nullが特殊なケースであること、およびポインターがオブジェクトを指す必要があることを明示的に述べていません。したがって、動作が定義されています。
1.標準の作成者が前に出てこない場合、または理由を明確にする根拠文書に類似したものを見つけられない限り。
はい。%p
変換指定子を使用してnullポインターを印刷すると、未定義の動作が発生します。そうは言っても、誤動作する既存の準拠する実装については知りません。
答えは、C標準(C89 / C99 / C11)のいずれかに適用されます。
%p
変換指定は、印刷可能な文字へのポインタの変換は実装定義され、空隙に型ポインタの引数を期待します。nullポインタが期待されることを示していません。
標準ライブラリ関数の概要では、(標準ライブラリ)関数への引数としてのnullポインタは、特に明記されていない限り、無効な値と見なされます。
C99
/ C11
§7.1.4 p1
[...]関数の引数に無効な値([...] nullポインタなど)がある場合、[...]動作は未定義です。
有効な引数としてnullポインタを期待する(標準ライブラリ)関数の例:
fflush()
「すべてのストリーム」(該当する)をフラッシュするためにnullポインターを使用します。freopen()
ストリームに「現在関連付けられている」ファイルを示すためにnullポインターを使用します。snprintf()
「n」がゼロの場合、nullポインタを渡すことができます。realloc()
新しいオブジェクトを割り当てるためにnullポインタを使用します。free()
nullポインタを渡すことができます。strtok()
後続の呼び出しにnullポインタを使用します。の場合snprintf()
、nがゼロのときにnullポインターを渡すことを許可することは理にかなっていますが、同様のゼロ「n」を許可する他の(標準ライブラリ)関数の場合はそうではありません。たとえば、次のようにmemcpy()
、memmove()
、strncpy()
、memset()
、memcmp()
。
これは、標準ライブラリの概要だけでなく、これらの関数の概要でも指定されています。
C99 §7.21.1 p2
/ C11 §7.24.1 p2
size_t
n として宣言された引数が関数の配列の長さを指定する場合、nはその関数の呼び出し時に値0を持つことができます。この節の特定の関数の説明で他に明示的に述べられていない限り、そのような呼び出しのポインター引数は、7.1.4で説明されているように有効な値を保持するものとします。
%p
nullポインターを持つUBが実際に意図的であるかどうかはわかりませんが、標準では、nullポインターは標準ライブラリ関数への引数として無効な値と見なされると明示的に規定されているため、nullの場合を明示的に指定していますポインタが有効な引数(snprintfの、自由、など)で、そしてそれが行くと、再びさえゼロに有効であるために、引数の必要性を繰り返し、「n」の例(memcpy
、memmove
、memset
)、そして、私はそれがあることを前提とするのが妥当だと思いますC標準委員会は、そのようなことを未定義にすることにあまり関心がありません。
%p
ことが未定義の動作ではないことに誰もが同意するだろう
C標準の作成者は、実装が特定の目的に適するために満たす必要のあるすべての動作要件を網羅的に列挙しようとはしませんでした。その代わり、彼らはコンパイラーを書いている人々が標準がそれを必要とするかどうかにかかわらず、ある程度の常識を行使するであろうと予想しました。
何かがUBを呼び出すかどうかの問題自体はめったに有用ではありません。重要な本当の質問は:
高品質のコンパイラを作成しようとしている人は、それを予測可能な方法で動作させる必要がありますか? 説明されているシナリオでは、答えは明らかにイエスです。
プログラマーは、通常のプラットフォームに似たものに対する高品質のコンパイラーが予測可能な方法で動作することを期待する資格を持つべきですか? 説明したシナリオでは、答えはイエスです。
一部の鈍感なコンパイラー作成者は、奇妙なことをすることを正当化するために、標準の解釈を拡張しているのではないでしょうか。 私はそうしないことを望みますが、それを排除するつもりはありません。
コンパイラをサニタイズすることで、その動作を誤解する必要がありますか?それはユーザーのパラノイアレベルに依存します。サニタイズコンパイラーは、おそらくデフォルトでそのような動作について不平を言うべきではありませんが、プログラムが奇妙に動作する「賢い」/ダムコンパイラーに移植される場合に備えて、構成オプションを提供する可能性があります。
標準の合理的な解釈が動作を定義することを意味するが、一部のコンパイラ作成者は解釈を拡張して別のことを行うことを正当化する場合、標準が言うことは本当に重要ですか?
printf("%p", (void*) 0)
、規格によると、未定義の動作かどうかです。深くネストされた関数呼び出しは、中国でのお茶の価格と同じくらい関連があります。そして、はい、UBは実際のプログラムでは非常に一般的です-何ですか?
%p
考えられるそれぞれの意味についての質問に答えました。