web-dev-qa-db-ja.com

Charポインターとprintf関数

ポインタを学習しようとしていて、ポインタの値を出力するために次のコードを書きました。

_#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つは理解できません。

  1. 2番目のケースでprintfがフォーマット指定子を必要としなかったのはなぜですか? printf(pointer_name)は、ポインターの値を出力するのに十分ですか?

  2. 私の理解によると(これはごくわずかです)、* pはabcを含む連続したメモリブロックを指します。両方の出力が同じになることを期待しました。

aBC

印刷方法が異なるため、出力も異なりますか?

編集1

さらに、次のコードは実行時エラーを生成します。なんでそうなの?

_ #include <stdio.h>

    int main(void) {
        char *p = "abc";
        printf(*p);
        return 0;
    }
_
10
Fabulous

最初の質問では、 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_で、プログラムが出力する文字列を探すアドレスです)。 未定義の動作になります。

16
  1. pはフォーマット文字列です。

    char *p = "abc";
    printf(p);
    

    と同じです

    print("abc");
    

    変数に何が含まれるかわからないため、これを行うのは非常に悪い習慣です。また、書式指定子が含まれている場合、printfを呼び出すと、非常に悪い結果が生じる可能性があります。

  2. 最初のケース("%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 */
    
8
Oskar Skog
  1. 2番目のケースでprintfがフォーマット指定子を必要としなかったのはなぜですか? printf(pointer_name)は、ポインターの値を出力するのに十分ですか?

"abc" is書式指定子。それが "abc"を出力する理由です。文字列に%が含まれていた場合、異常な動作が行われていましたが、そうではありませんでした。

printf("abc"); // Perfectly fine!
  1. 2番目のケースでprintfがフォーマット指定子を必要としなかったのはなぜですか? printf(pointer_name)は、ポインターの値を出力するのに十分ですか?

%cは文字変換指定子です。最初のバイトのみを出力するようにprintfに指示します。文字列を出力したい場合は、...

printf ("%s", p);

%sは冗長に見えますが、制御文字の印刷や、幅指定子を使用する場合に役立ちます。


これを実際に理解するための最良の方法は、printfを使用して文字列abc%defを試し印刷することです。

2
QuestionC

2番目のケースでprintfがフォーマット指定子を必要としなかったのはなぜですか? printf(pointer_name)は、ポインターの値を出力するのに十分ですか?

コードを使用して、printfに文字列をフォーマット文字列として使用するように指示しました。コードがprintf("abc")と同等になることを意味します。

私の理解によると(これはごくわずかです)、* pはabcを含む連続したメモリブロックを指します。両方の出力が同じであることを期待していました

_%c_を使用すると文字が出力され、_%s_を使用すると文字列全体が取得されます。しかし、printfに文字列をフォーマット文字列として使用するように指示すると、それも実行されます。

char *p = "abc"; printf(*p);

このコードは、p、文字_'a'_の内容がフォーマット文字列へのポインタではなく、ポインタでもないため、クラッシュします。そのコードは警告なしでコンパイルするべきではありません。

2
Lundin

あなたは誤解しています

char *p = "Hello";

pは、リテラル「Hello」が格納されている開始アドレスを指します。これがあなたのやり方です宣言ポインタ。ただし、その後、

*p

dereferencepを意味し、pが指すオブジェクトを取得します。上記の例では、これは「H」になります。これにより、2番目の質問が明確になります。

Printfの場合は、

printf("Hello");

これも問題ありません。これは最初の質問に答えます。なぜなら、printfにpを渡したときと実質的に同じだからです。


最後にあなたの編集に、確かに

printf(*p);

上記の行は正しくありません。printfはconst char *を想定しているため、*pを使用すると、charに渡されます。つまり、この例では「H」が渡されます。逆参照の意味については、こちらをご覧ください。

2
giorgim

%c書式指定子はcharタイプを期待し、singlechar値を出力します。

printfの最初のパラメーターはconst char*char*は暗黙的にconst char*に変換できる)でなければならず、startstring文字数。その文字列で\0を検出すると、印刷を停止します。その文字列に\0が存在しない場合、その関数の動作はndefinedです。 "abc"にはフォーマット指定子が含まれていないため、その場合はprintfに追加の引数を渡しません。

1
Bathsheba