ポインタを学習しようとしていて、ポインタの値を出力するために次のコードを書きました。
_#include <stdio.h>
int main(void) {
char *p = "abc";
printf("%c",*p);
return 0;
}
_
出力は次のとおりです。
a
ただし、上記のコードを次のように変更すると、
_#include <stdio.h>
int main(void) {
char *p = "abc";
printf(p);
return 0;
}
_
私は出力を取得します:
aBC
次の2つは理解できません。
2番目のケースでprintfがフォーマット指定子を必要としなかったのはなぜですか? printf(pointer_name)
は、ポインターの値を出力するのに十分ですか?
私の理解によると(これはごくわずかです)、* pはabc
を含む連続したメモリブロックを指します。両方の出力が同じになることを期待しました。
aBC
印刷方法が異なるため、出力も異なりますか?
編集1
さらに、次のコードは実行時エラーを生成します。なんでそうなの?
_ #include <stdio.h>
int main(void) {
char *p = "abc";
printf(*p);
return 0;
}
_
最初の質問では、 printf
function(and family) は最初の引数として文字列を取ります(つまり、_const char *
_)。その文字列には、printf
関数が対応する引数で置き換えるフォーマットコードを含めることができます。残りのテキストはそのまま、そのままの形で印刷されます。そして、それが最初の引数としてp
を渡したときに何が起こっているかです。
この方法でprintf
を使用することは、特に文字列にユーザーからの入力が含まれている場合は、あまりお勧めできません。ユーザーが文字列にフォーマットコードを追加し、正しい引数を指定しないと、未定義の動作になります。セキュリティホールにつながる可能性さえあります。
2番目の質問では、変数p
がメモリをポイントしています。式_*p
_は、ポインターを逆参照して単一の文字、つまりp
が実際に指している文字、つまり_p[0]
_を提供します。
p
は次のように考えてください。
+ --- + + ----- + ----- + ----- + ------ + | p | ---> | 'a' | 'b' | 'c' | '\ 0' | + --- + + ----- + ----- + ----- + ------ +
変数p
が実際に指すのは「文字列」ではなく、メモリ内の単一の場所、つまり文字列_"abc"
_の最初の文字のみを指します。そのメモリを文字のシーケンスとして扱うのは、p
を使用する関数です。
さらに、定数文字列リテラルは、実際には、文字列の文字数に文字列ターミネーターの文字数を加えた数の(読み取り専用)配列として格納されます。
また、_*p
_が_p[0]
_と同じである理由を理解するには、ポインタまたは配列p
について知っておく必要があります。有効なインデックスi
、式_p[i]
_は*(p + i)
と等しい。最初の文字を取得するには、インデックス_0
_があります。つまり、_p[0]
_があり、これは*(p + 0)
と等しくなるはずです。何にもゼロを追加することは何もしないので、*(p + 0)
は*(p)
と同じで、_*p
_と同じです。したがって、_p[0]
_は_*p
_と同じです。
編集について(printf(*p)
を実行する場合)、_*p
_はp
(つまり_p[0]
_)が指す最初の「要素」の値を返すため、1文字をフォーマット文字列へのポインタとして使用します。これにより、コンパイラーは、その単一文字の値を持つアドレスを指すポインターに変換します(文字をポインターに変換しませんto文字)。このアドレスは非常に有効なアドレスではありません( ASCIIアルファベット _'a'
_の値は_97
_で、プログラムが出力する文字列を探すアドレスです)。 未定義の動作になります。
p
はフォーマット文字列です。
char *p = "abc";
printf(p);
と同じです
print("abc");
変数に何が含まれるかわからないため、これを行うのは非常に悪い習慣です。また、書式指定子が含まれている場合、printf
を呼び出すと、非常に悪い結果が生じる可能性があります。
最初のケース("%c"
)最初の文字だけが表示される%c
はバイトを意味し、*p
は、p
が指している(最初の)値を意味します。
%s
は文字列全体を出力します。
char *p = "abc";
printf(p); /* If p is untrusted, bad things will happen, otherwise the string p is written. */
printf("%c", *p); /* print the first byte in the string p */
printf("%s", p); /* print the string p */
- 2番目のケースでprintfがフォーマット指定子を必要としなかったのはなぜですか? printf(pointer_name)は、ポインターの値を出力するのに十分ですか?
"abc" is書式指定子。それが "abc"を出力する理由です。文字列に%
が含まれていた場合、異常な動作が行われていましたが、そうではありませんでした。
printf("abc"); // Perfectly fine!
- 2番目のケースでprintfがフォーマット指定子を必要としなかったのはなぜですか? printf(pointer_name)は、ポインターの値を出力するのに十分ですか?
%c
は文字変換指定子です。最初のバイトのみを出力するようにprintf
に指示します。文字列を出力したい場合は、...
printf ("%s", p);
%s
は冗長に見えますが、制御文字の印刷や、幅指定子を使用する場合に役立ちます。
これを実際に理解するための最良の方法は、printf
を使用して文字列abc%def
を試し印刷することです。
2番目のケースでprintfがフォーマット指定子を必要としなかったのはなぜですか? printf(pointer_name)は、ポインターの値を出力するのに十分ですか?
コードを使用して、printfに文字列をフォーマット文字列として使用するように指示しました。コードがprintf("abc")
と同等になることを意味します。
私の理解によると(これはごくわずかです)、* pはabcを含む連続したメモリブロックを指します。両方の出力が同じであることを期待していました
_%c
_を使用すると文字が出力され、_%s
_を使用すると文字列全体が取得されます。しかし、printfに文字列をフォーマット文字列として使用するように指示すると、それも実行されます。
char *p = "abc"; printf(*p);
このコードは、p
、文字_'a'
_の内容がフォーマット文字列へのポインタではなく、ポインタでもないため、クラッシュします。そのコードは警告なしでコンパイルするべきではありません。
あなたは誤解しています
char *p = "Hello";
p
は、リテラル「Hello」が格納されている開始アドレスを指します。これがあなたのやり方です宣言ポインタ。ただし、その後、
*p
dereferencep
を意味し、p
が指すオブジェクトを取得します。上記の例では、これは「H」になります。これにより、2番目の質問が明確になります。
Printfの場合は、
printf("Hello");
これも問題ありません。これは最初の質問に答えます。なぜなら、printfにp
を渡したときと実質的に同じだからです。
最後にあなたの編集に、確かに
printf(*p);
上記の行は正しくありません。printfはconst char *
を想定しているため、*p
を使用すると、char
に渡されます。つまり、この例では「H」が渡されます。逆参照の意味については、こちらをご覧ください。
%c
書式指定子はchar
タイプを期待し、singlechar
値を出力します。
printf
の最初のパラメーターはconst char*
(char*
は暗黙的にconst char*
に変換できる)でなければならず、startのstring文字数。その文字列で\0
を検出すると、印刷を停止します。その文字列に\0
が存在しない場合、その関数の動作はndefinedです。 "abc"
にはフォーマット指定子が含まれていないため、その場合はprintf
に追加の引数を渡しません。