%pを使用してnullポインターを印刷することは未定義の動作ですか?


93

%p変換指定子を使用してnullポインターを出力するのは未定義の動作ですか?

#include <stdio.h>

int main(void) {
    void *p = NULL;

    printf("%p", p);

    return 0;
}

質問はC標準に適用され、C実装には適用されません。


(C委員会を含む)誰もがそれをあまり気にしていないと実際には思わない。これは、実際的な意味がない(またはほとんどない)かなり人工的な問題です。
P__J__ 2017

これは、printfが値を表示するだけで、触れない(ポイントされたオブジェクトの読み取りまたは書き込みの意味で)
-UB

3
@PeterJは、あなたが言っていることが真実であるとしましょう(明らかに標準の状態はそうではありません)。これについて議論しているという事実だけで、以下の標準の引用部分のように、質問は有効で正しいものになります通常の開発者にとって、一体何が起こっているのかを理解することは非常に困難です。
Peter Varo 2017


2
@PeterJそれは別の話です、明確化に感謝します:)
Peter Varo

回答:


93

これは、私たちが英語の制限と標準の一貫性のない構造の影響を受ける奇妙なコーナーケースの1つです。だから、せいぜい、それを証明することは不可能なので、私は説得力のある反論をすることができます:) 1


問題のコードは、明確に定義された動作を示しています。

[7.1.4]は質問の根拠がある、のが起動してみましょう:

以下の各説明は、以下の詳細な説明で特に明記されていない限り適用されます。関数への引数に無効な値(関数のドメイン外の値やプログラムのアドレス空間外のポインターなど)がある場合、またはnullポインタ[...その他の例...][...]動作は未定義です。[...その他のステートメント...]

これは不器用な言葉です。1つの解釈は、リスト内の項目は、個々の説明で上書きされない限り、すべてのライブラリー関数のUBであるというものです。しかし、リストは「など」で始まり、それが例示であって網羅的ではないことを示しています。たとえば、文字列の正しいnull終了については触れていません(egの動作にとって重要ですstrcpy)。

したがって、7.1.4の意図/範囲が「無効な値」がUBにつながるということは明らかです(特に明記されていない限り)。どの関数が「無効な値」としてカウントされるかを判断するには、各関数の説明を調べる必要があります。

例1- 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

例2- printf

[7.19.6.1]はこれについて%p次のように述べています:

p-引数はへのポインタvoidです。ポインターの値は、実装定義の方法で、印刷文字のシーケンスに変換されます。

Nullは有効なポインター値であり、このセクションでは、nullが特殊なケースであること、およびポインターがオブジェクトを指す必要があることを明示的に述べていません。したがって、動作が定義されています。


1.標準の作成者が前に出てこない場合、または理由を明確にする根拠文書に類似したものを見つけられない限り。


コメントは、詳細な議論のためのものではありません。この会話はチャットに移動さました
Bhargav Rao

1
「それでもヌルターミネータについては言及していません」は、例1では弱い-仕様に「文字列をコピーする」というstrcpyがあります文字列は、null文字を含むものとして明示的に定義されています
chux-モニカを2017年

1
@chux-それはやや私のポイントです-7.1.4 のリストが網羅的であると想定するのではなく、コンテキストから有効/無効なものを推測する必要があります。(ただし、私の回答のこの部分の存在は、strcpyが反例であると主張してから削除されたコメントのコンテキストでは、いくぶん意味がありました。)
Oliver Charlesworth

1
問題の核心は、読者がどのように解釈するかであるような。それはどういう意味のいくつかの例の可能な無効な値がありますかそれは常に無効な値であるいくつかの例があるということですか?記録のために、私は最初の解釈で行きます。
ninjalj 2017

1
@ninjalj-はい、同意しました。これは、本質的に私がここで私の答えで伝えようとしていることです。つまり、「これらは無効な値である可能性のあるタイプの例です」。:)
オリバーチャールズワース2017

20

短い答え

はい%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_tn として宣言された引数が関数の配列の長さを指定する場合、nはその関数の呼び出し時に値0を持つことができます。この節の特定の関数の説明で他に明示的に述べられていない限り、そのような呼び出しのポインター引数は、7.1.4で説明されているように有効な値を保持するものとします。


それは意図的ですか?

%pnullポインターを持つUBが実際に意図的であるかどうかはわかりませんが、標準では、nullポインターは標準ライブラリ関数への引数として無効な値と見なされると明示的に規定されているため、nullの場合を明示的に指定していますポインタが有効な引数(snprintfの、自由、など)で、そしてそれが行くと、再びさえゼロに有効であるために、引数の必要性を繰り返し、「n」の例(memcpymemmovememset)、そして、私はそれがあることを前提とするのが妥当だと思いますC標準委員会は、そのようなことを未定義にすることにあまり関心がありません。


コメントは、詳細な議論のためのものではありません。この会話はチャットに移動さました
Bhargav Rao

1
@JeroenMostert:この議論の意図は何ですか?7.1.4の与えられた引用はかなり明確です、そうではありませんか?議論するためには何がある「特に断らない限り、」それがされたときにされていない、特に明記?(無関係の)文字列関数ライブラリに同様の文言があるため、その文言が偶然ではないように見えるという事実について何を議論する必要がありますか?私はこの答え(ながら、実際には有用ではないと思う実際には)可能な限り正確です。
デイモン2017

3
@Damon:神話的なハードウェアは神話的ではありません。有効なアドレスを表さない値がアドレスレジスタにロードされない可能性がある多くのアーキテクチャがあります。ただし、これらのプラットフォームで一般的なメカニズムとして機能するには、関数の引数としてnullポインターを渡す必要があります。単に1つをスタックに置いても、問題は解決しません。
Jeroen Mostert 2017

1
@anatolyg:x86プロセッサでは、アドレスには2つの部分(セグメントとオフセット)があります。8086では、セグメントレジスタのロードは他のロードと似ていますが、それ以降のすべてのマシンでは、セグメント記述子をフェッチします。無効な記述子をロードすると、トラップが発生します。ただし、80386以降のプロセッサのコードの多くは1つのセグメントしか使用しないため、セグメントレジスタをまったくロードしません。
スーパーキャット2017

1
でヌルポインタを印刷する%pことが未定義の動作ではないことに誰もが同意するだろう
MM

-1

C標準の作成者は、実装が特定の目的に適するために満たす必要のあるすべての動作要件を網羅的に列挙しようとはしませんでした。その代わり、彼らはコンパイラーを書いている人々が標準がそれを必要とするかどうかにかかわらず、ある程度の常識を行使するであろうと予想しました。

何かがUBを呼び出すかどうかの問題自体はめったに有用ではありません。重要な本当の質問は:

  1. 高品質のコンパイラを作成しようとしている人は、それを予測可能な方法で動作させる必要がありますか? 説明されているシナリオでは、答えは明らかにイエスです。

  2. プログラマーは、通常のプラットフォームに似たものに対する高品質のコンパイラーが予測可能な方法で動作することを期待する資格を持つべきですか? 説明したシナリオでは、答えはイエスです。

  3. 一部の鈍感なコンパイラー作成者は、奇妙なことをすることを正当化するために、標準の解釈を拡張しているのではないでしょうか。 私はそうしないことを望みますが、それを排除するつもりはありません。

  4. コンパイラをサニタイズすることで、その動作を誤解する必要がありますか?それはユーザーのパラノイアレベルに依存します。サニタイズコンパイラーは、おそらくデフォルトでそのような動作について不平を言うべきではありませんが、プログラムが奇妙に動作する「賢い」/ダムコンパイラーに移植される場合に備えて、構成オプションを提供する可能性があります。

標準の合理的な解釈が動作を定義することを意味するが、一部のコンパイラ作成者は解釈を拡張して別のことを行うことを正当化する場合、標準が言うことは本当に重要ですか?


1.プログラマーが、現代の/積極的なオプティマイザーによって行われた仮定が、「合理的」または「品質」であると考えるものと矛盾することを見つけるのは珍しいことではありません。2.仕様のあいまいさに関して言えば、実装者がどのような自由を想定するかについて矛盾することも珍しくありません。3. C標準委員会のメンバーになると、「正しい」解釈が何であるかはもちろんのこと、彼らが常に「正しい」解釈について同意するわけでありません。前述のことを踏まえると、どの合理的な解釈に従う必要がありますか?
Dror

6
「この特定のコードはUBを呼び出すかどうか」という質問に、UBの有用性についての論文やコンパイラーがどのように動作するかについての論文に答えるのは、特に次のようにコピーして貼り付けることができるため、答えとしては不十分です。特定のUBに関するほとんどすべての質問に対する回答。あなたの修辞的な繁栄への喜びとして:はい、標準はプログラマーとコンパイラー作成者の両方が出発点であるため、一部のコンパイラー作成者が何をしたか、またはあなたがそうすることについて彼らが何を考えているかに関係なく、それは本当に標準が言うことに関係があります。
Jeroen Mostert

1
@JeroenMostert:「Does Xは未定義の動作を起動する」への答えは、多くの場合、質問の意味によって異なります。標準が準拠する実装の動作に要件を課さない場合、プログラムが未定義の動作をしていると見なされると、ほぼすべてのプログラムがUBを呼び出します。標準の作成者は、プログラムが関数呼び出しを深くネストしている場合でも、実装がStadardで翻訳制限を行使する少なくとも1つの(おそらくは不自然な)ソーステキストを正しく処理できる限り、実装が任意の方法で動作することを明らかに許可しています。
スーパーキャット2017

@supercat:非常に興味深いですがprintf("%p", (void*) 0)、規格によると、未定義の動作かどうかです。深くネストされた関数呼び出しは、中国でのお茶の価格と同じくらい関連があります。そして、はい、UBは実際のプログラムでは非常に一般的です-何ですか?
Jeroen Mostert 2017

1
@JeroenMostert:標準では、鈍角な実装がほとんどすべてのプログラムにUBがあると見なすことができるため、重要なのは鈍角でない実装の動作です。気付かなかったかもしれませんが、私はUBについてのコピー/貼り付けを作成するのではなく、質問の%p考えられるそれぞれの意味についての質問に答えました。
スーパーキャット2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.