1. ホーム
  2. c++

[解決済み] ポインタの表現。*ptr++、*++ptr、++*ptr。

2022-05-14 23:17:53

質問

最近、自分では理解できない問題に遭遇しました。

この3つの式は何でしょうか? REALLY は何を意味するのでしょうか?

*ptr++
*++ptr
++*ptr

私はRitchieを試しました。しかし、残念ながら、これらの3つの操作について彼が話したことに従うことはできませんでした。

これらはすべてポインタ/指された値をインクリメントするために実行されることは知っています。また、優先順位や評価の順番など、さまざまなことがあるのだろうと推測できます。たとえば、最初にポインターをインクリメントしてからそのポインターのコンテンツをフェッチする、コンテンツを単にフェッチしてからポインターをインクリメントする、などなどです。おわかりのように、私は彼らの 実際の の操作について明確な理解がないので、できるだけ早くそれをクリアしたいと思います。でも、実際にプログラムで使うときは、本当に困りますね。例えば

int main()
{
    char *p = "Hello";
    while(*p++)
         printf("%c",*p);
    return 0;
}

はこのような出力をします。

ello

しかし、私の予想では、それは Hello . 最後のお願いです -- それぞれの式が与えられたコードスニペットでどのように機能するのか、例を挙げてください。ほとんどの場合、理論の単なる段落が私の頭の上を飛び交うだけなので。

どのように解決するのですか?

以下は、役に立つと思われる詳細な説明です。まず、あなたのプログラムについて説明します。

int main()
{
    char *p = "Hello";
    while(*p++)
        printf("%c",*p);
    return 0;
}

最初の文です。

char* p = "Hello";

p へのポインタとして char . へのポインタと言う場合、そのポインタは char というのはどういう意味でしょうか?それは p のアドレスが char ; p は、メモリ上のどこに char .

このステートメントはまた p の最初の文字を指すように初期化します。 "Hello" . この演習では、文字列リテラルである p は文字列全体ではなく、最初の文字だけを指していると理解することが重要です。 'H' . 結局のところ p へのポインタは、1つの char へのポインタであり、文字列全体へのポインタではない。の値は p のアドレスは 'H'"Hello" .

そして、ループを設定します。

while (*p++)

ループ条件とは *p++ は何を意味するのでしょうか?少なくとも慣れるまでは)3つのことが作用して、これを不可解にしています。

  1. 2 つの演算子の優先順位、postfix ++ とインダイレクト *
  2. ポストフィックスインクリメント式の値
  3. postfixインクリメント式の副次効果

1. 優先順位 . 演算子の優先順位表をざっと見たところ、後置加算の優先順位(16)は、参照・非参照(15)よりも高いことがわかります。これは、複合式 *p++ は次のようにグループ化されることになります。 *(p++) . つまり * の部分は p++ の部分の値に適用されます。それでは p++ の部分を最初に見てみましょう。

2. 後置表現値 . の値は p++ の値は p インクリメントの前に . 持っている場合。

int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);

とすると、出力されます。

7
8

なぜなら i++ と評価されるからです。 i と評価されます。同様に p++ の現在の値として評価されます。 p . ご存知のように、現在の値である p のアドレスは 'H' .

そこで今度は p++ の部分は *p++ の部分が評価されました。 p . 次に * の部分が起こります。 *(current value of p) が保持するアドレスの値にアクセスする、という意味です。 p . そのアドレスにある値は 'H' . ということは、式 *p++ は次のように評価されます。 'H' .

ちょっと待てよ、ということですね。もし *p++ に評価されるのは 'H' と評価されるのに、なぜ 'H' と表示されるでしょうか?それは サイドエフェクト が出てくるのです。

3. Postfix式の副作用 . 後置詞の ++ には の値 は、現在のオペランドの 副作用 という副作用があります。え?それを見てみましょう。 int のコードをもう一度見てください。

int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);

先述の通り、出力されるのは

7
8

いつ i++ が評価されると、最初の printf() で評価されると 7 と評価されます。 しかし、C 標準では 2 番目の printf() が実行を開始する前のある時点で 副作用 ++ 演算子の副作用 が発生します。つまり、2番目の printf() が起こる。 i の結果、インクリメントされることになります。 ++ 演算子によって増分されます。 printf() . ところで、これは副作用のタイミングについて標準が与える数少ない保証の1つです。

では、あなたのコードにおいて、式 *p++ が評価されると、それは 'H' . しかし、これに到達するまでに

printf ("%c", *p)

という厄介な副作用が発生しました。 p がインクリメントされました。おっと! それはもはや 'H' を指しているのではなく、一文字前の 'H' になります。 'e' を、言い換えれば それで、あなたのコックリさんの出力が説明できるのです。

ello

それゆえ、他の回答では、役に立つ(そして正確な)提案の大合唱となりました:Received Pronunciationを印刷するために "Hello" を印刷するには、次のようなものが必要です。

while (*p)
    printf ("%c", *p++);

これくらいにしておきましょう。残りはどうでしょうか?これらの意味についてお聞きします。

*ptr++
*++ptr
++*ptr

1つ目について話したばかりなので、2つ目について見てみましょう。 *++ptr .

先ほどの説明で、postfixのインクリメントが p++ には、ある 優先順位 , a であり 副作用 . 接頭辞のインクリメント ++p は、同じ 副作用 オペランドを 1 だけ増加させるというものです。 優先順位 であり、異なる .

接頭辞のインクリメントは接尾辞よりも優先順位が低く、優先順位は15である。言い換えれば、これは参照/間接演算子 * . のような式では

*++ptr

は優先順位ではありません。2つの演算子の優先順位は同じです。つまり 連想性 が働きます。プリフィックスインクリメントとインダイレクト演算子には右左の連想性があります。その連想性のために、オペランド ptr はグループ化され、一番右の演算子 ++ よりも左の演算子の前に * . つまり、式はグループ化されることになり *(++ptr) . ですから *ptr++ と同じですが、違う理由で、ここでも * の部分が適用されることになります。 ++ptr の部分に適用されます。

では、その値とは何でしょうか。プリフィックスインクリメント式の値は、オペランド の値で、インクリメントの後に . このため、後置インクリメント演算子とは全く異なるものとなっている。例えば、次のようなものがあるとしよう。

int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);

と出力されます。

8
8

...postfix演算子で見たのとは違いますね。同じように、もし

char* p = "Hello";
printf ("%c ", *p);    // note space in format string
printf ("%c ", *++p);  // value of ++p is p after the increment
printf ("%c ", *p++);  // value of p++ is p before the increment
printf ("%c ", *p);    // value of p has been incremented as a side effect of p++

とすると、出力されます。

H e e l                // good dog

なぜかわかりますか?

さて、ご質問の3つ目の表現に入ります。 ++*ptr . 実はこれが一番厄介なんです。どちらの演算子も同じ優先順位を持ち、右から左へ連想させることができます。つまり、式はグループ化されることになります ++(*ptr) . は ++ の部分は *ptr の部分の値に適用されます。

ということで、もし

char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);

という、驚くほどエゴイスティックな出力になりそうです。

I

なんですって!?じゃあ、その *p の部分は、次のように評価されます。 'H' . そして ++ が適用されるようになりますが、その時点で 'H' に適用され、ポインターには全く適用されません。に1を足すとどうなるでしょうか? 'H' ? の ASCII 値に 1 を足したものになります。 'H' のアスキー値72を足すと73になります。として表現します。 char と表せば、次のようになります。 char をASCII値73で表示します。 'I' .

これで、ご質問の3つの表現が解決しました。もうひとつは、ご質問の最初のコメントで紹介したものです。

(*ptr)++ 

これも面白いですね。もしあれば。

char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);

を実行すると、このような熱狂的な出力が得られます。

HI
    

どうしたんだ?繰り返しになりますが、それは 優先順位 , 表現値 であり、かつ 副作用 . 括弧があるため *p の部分は一次式として扱われます。一次式は他のすべての式に優先し、最初に評価されます。そして *p は、ご存知のように、次のように評価されます。 'H' . 式の残りの部分、つまり ++ の部分は、その値に適用される。つまり、この場合 (*p)++'H'++ .

の値は何ですか? 'H'++ ? もし、あなたが 'I' と言ったなら、あなたは(もう!)ポストフィックスインクリメントでの値対副作用の議論を忘れています。思い出してください。 'H'++ の現在値 'H' . そのため、最初の printf() が表示されます。 'H' . 次に 副次的効果 として、その 'H' にインクリメントされます。 'I' . 2番目の printf() が表示されます。 'I' . そして、元気な挨拶ができます。

さて、最後の2つのケースですが、どうして

char q[] = "Hello";
char* p = q;

のようなものではだめなのでしょうか?

char* p = "Hello";
printf ("%c", ++*p);   // attempting to change string literal!

なぜなら "Hello" は文字列リテラルだからです。もしあなたが ++*p とすると、あなたは 'H' という文字列を 'I' とすることで、文字列全体を "Iello" . C言語では、文字列リテラルは読み取り専用で、変更しようとすると未定義の動作になります。 "Iello" は英語でも未定義ですが、それは単なる偶然です。

逆に言えば

char p[] = "Hello";
printf ("%c", *++p);  // attempting to modify value of array identifier!

なぜダメなのか?なぜなら、この例では p は配列だからです。配列は変更可能なL値ではありません。 p が指す場所を変更することはできません。なぜなら、配列の名前はあたかもそれが定数ポインタであるかのように動作するからです。(実際にはそうではなく、便利な見方をしているだけなのですが)。

まとめると、ご質問の3点です。

*ptr++   // effectively dereferences the pointer, then increments the pointer
*++ptr   // effectively increments the pointer, then dereferences the pointer
++*ptr   // effectively dereferences the pointer, then increments dereferenced value

そして4つ目は、他の3つに負けず劣らず楽しいものです。

(*ptr)++ // effectively forces a dereference, then increments dereferenced value

1番目と2番目は、以下の場合にクラッシュします。 ptr が実際に配列識別子であった場合、クラッシュします。3番目と4番目は、もし ptr が文字列リテラルを指しているとクラッシュします。

これで完成です。これですべてのクリスタルが揃ったかと思います。皆さんは素晴らしい聴衆でした。私は今週ずっとここにいます。