1

4がセグメンテーション違反を生成するのに、なぜ1、2、および3が機能するのですか?(下記参照。)

char c[10];
char* d;

1.1。

scanf("%s", &c);
printf("%s\n", &c);

2.2。

scanf("%s", c);
printf("%s\n", c);

3.3。

scanf("%s", &d);
printf("%s\n", &d);

4.4。

scanf("%s", d);
printf("%s\n", d);
4

4 に答える 4

4

質問のコードを繰り返す:

char c[10];
char* d;

1.1。

scanf("%s", &c);
printf("%s\n", &c);

これは期待どおりに機能する可能性がありますが、実際の動作は未定義です。

scanfフォーマットを使用する"%s"には、タイプの引数が必要char*です。 &cタイプchar (*)[10]は、つまり、char[10]配列へのポインタです。の0番目の要素のアドレスと同じメモリ内の場所を指しますが、cタイプが異なります。同じことがprintf::"%s"形式でも引数を期待するように指示されchar*ますが、引数を渡しchar(*)[10]ます。

は可変個引数関数であるためscanf、フォーマット文字列以外の引数の型チェックは必要ありません。コンパイラーは、値を処理できると仮定して、(おそらく)char (*)[10]値をに喜んで渡します。scanfそして、すべてのポインターが同じサイズ、表現、および引数受け渡しメカニズムを持つ実装では、おそらく可能です。char*ただし、たとえば、エキゾチックアーキテクチャ用のCコンパイラは、ポインタをより大きな型へのポインタよりも簡単に大きくすることができます。たとえば、ネイティブアドレスが64ビットワードを指しているCPUを想像してみてください。char*ポインタは、ワードポインタとバイトオフセットで構成されている場合があります。

2.2。

scanf("%s", c);
printf("%s\n", c);

これの方が良い。 cは配列ですが、このコンテキストでは、配列式は配列の最初の要素へのポインタに「減衰」します。これはscanf"%s"フォーマットで必要なものとまったく同じです。同じことがに渡さcprintfます。(しかし、まだいくつかの問題があります。他の例の後でそれについて説明します。

3.3。

scanf("%s", &d);
printf("%s\n", &d);

dは単一のchar*引数で&dあり、型であるためchar**、ここでも、間違った型の引数を渡しています。すべてのポインタが同じ表現(および同じ引数受け渡しメカニズム)を持ち、の入力がscanf十分に短い場合、これは「機能する」可能性があります。char*オブジェクトをの配列であるかのように扱いますchar。が4バイトで、入力文字列の長さが3文字以下の場合char*、これはおそらく機能します。つまり、を使用しchar[4]て呼び出しを正しく記述したかのように機能します。しかし、それは非常に文字列をポインタオブジェクトに直接格納するのは不適切な方法であり、オブジェクトの終わりを超えて書き込むリスクが非常に高く、予測できない結果になります。(これらの予測できない結果には、他の目的に使用されていないメモリへの書き込みが含まれます。これは、機能しているように見える可能性があります。これは、未定義の動作の性質です。)

(C標準では、オブジェクトを文字の配列として扱うための特別な許可が与えられていますが、この場合は非常に悪い考えです。)

4.4。

scanf("%s", d);
printf("%s\n", d);

ここではタイプはすべて正しいですがd、十分に大きな配列を指すように初期化しない限りchar、見事に失敗する可能性があります(または、さらに悪いことに、「正しく」機能しているように見えます。つまり、微妙なバグがあります。おそらく後で表示されます)。

そして今、私たちは他の問題について上で述べたことに到達します。

たとえば、4ではd、「十分に大きい」配列を指す必要があると述べました。「十分に大きい」の大きさはどれくらいですか?それに対する答えはありません。 scanf("%s", ...)長さに上限がない、空白で区切られた文字のシーケンスを読み取ります。たとえば、プログラムを実行してxキーを押したままにすると、提供したどのバッファーよりも長い入力文字列を提供でき、予測できない結果が発生します(再び未定義の動作)。

scanf関数の形式"%s"を安全に使用することはできません(標準の入力ストリームに表示される内容を制御できる環境でプログラムを実行しない限り)。

テキスト入力を読み取る良い方法の1つは、を使用fgetsして一度に1行を読み取り、他の関数を使用して結果を分析することです。 fgets入力の最大長を指定する必要があります。実際の入力が制限を超えた場合、それは切り捨てられ、後の呼び出しで読み取られるように残されます。ほど便利ではありませんscanfが、安全に行うことができます。(また、この関数は絶対に使用しないでください。たとえば、安全に使用することはできません。)getsscanf("%s", ...)

推奨読書:

comp.lang.c FAQのセクション6は、C配列とポインター、およびそれらがどのように関連しているか(および関連していないか)を説明する優れた仕事をしています。セクション12では、C標準I/Oについて説明します。

(この回答が長すぎて申し訳ありません。短くする時間がありませんでした。)

于 2012-04-15T01:40:27.743 に答える
3

ケース3と4で未定義の動作が発生しました。

  • ケース1と2は同じで、どちらも配列の最初の要素を指しています。
  • ケース3は未定義です。これは、charへのポインターを期待するときにcharへのポインターへのポインターを指定するためです。
  • dポインタが初期化されていないため、ケース4は未定義です。

于 2012-04-15T00:37:26.083 に答える
2

3は動作します(多くのプラットフォームで、オンにすると警告が表示されます。技術的には未定義の動作です)。これは、ポインタを悪用しているためです(&dポインタ用のメモリ内に文字を格納する(char **)ように処理します)。 (char *)。初期化されていないポインタがランダムアドレスを指しているため、4が停止します。

于 2012-04-15T00:39:30.640 に答える
0

ここで重要なのは、結果を保存するスペースがあるかどうかです。

scanf("%s", &c);
printf("%s\n", &c);

ストレージはありますか?はい、取得するアドレスは配列の最初の要素のアドレスです。配列が存在するので、そこに結果を置くことができます。

scanf("%s", c);
printf("%s\n", c);

ストレージはありますか?はい。このように使用すると、配列はポインタに折りたたまれ、上記と同じように渡されます。

scanf("%s", &d);
printf("%s\n", &d);

ストレージはありますか?はい。これは適切な型ではありませんが(char **、である必要がありますchar *)、charをポインタ型にキャストし、ポインタとして宣言された変数に格納するのと何ら変わりはありません。(他の回答では、これは未定義の動作であると言われています。不適切な場合は、または他charの整数型をchar *または他のポインター型にキャストすることは明確に定義されています。標準でこれが未定義であると示されている場所を示してください。 )。

scanf("%s", d);
printf("%s\n", d);

ストレージはありますか?割り当てたわけではありません。技術的には、dセグメンテーション違反が発生しないメモリ内の場所を指している場合があります。たとえそうだとしても、それはあなたの記憶ではなく、あなたは何か重要なことを上書きしているかもしれませんし、あるいはそれは予期せずに変わるかもしれません。あなたはdポイントする有効なメモリをどこで見つけるかを教えていないので、あなたはポインタロシアンルーレットをプレイしています。

于 2012-04-15T01:25:09.743 に答える