最近、私自身では理解できないこの問題に遭遇しました。
これら3つの表現は何を意味するのか本当に平均?
*ptr++
*++ptr
++*ptr
私はリッチーを試しました。しかし残念ながら、これら 3 つの操作について彼が言ったことに従うことができませんでした。
これらはすべて、ポインター/指し示す値を増分するために実行されることは知っています。また、優先順位や評価の順序についても多くのことがあると推測できます。たとえば、最初にポインターを増分してからそのポインターのコンテンツを取得する、単にコンテンツを取得してからポインターを増分するなどです。ご覧のとおり、私はそれらについて明確な理解を持っていません。実際のできるだけ早くクリアしたい操作です。しかし、プログラムに適用する機会があると本当に困ってしまいます。たとえば、
int main()
{
char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
次のような出力が得られます:
ello
しかし、私は次のように印刷されると思っていましたHello
。最後にもう 1 つお願いがあります。特定のコード スニペットで各式がどのように機能するかの例を教えてください。ほとんどの場合、理論のほんの一節しか理解できないからです。
ベストアンサー1
役に立つと思われる詳細な説明を次に示します。説明が最も簡単なプログラムから始めましょう。
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 つのことが起こっており、これが不可解です。
- 2つの演算子、後置演算子
++
と間接演算子の優先順位*
- 後置増分式の値
- 後置増分式の副作用
1. 優先順位ざっと見てみると演算子の優先順位表は、ポストフィックスのインクリメントが逆参照/間接参照よりも優先順位が高いことを示しています。これは、複雑な式が次*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. 後置式の副作用接尾辞++
には価値現在のオペランドの副作用オペランドを増分するのです。えっ?int
もう一度コードを見てみましょう:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
前述のとおり、出力は次のようになります。
7
8
i++
が最初の で評価されるとprintf()
、7 と評価されます。しかし、C 標準では、2 番目の のprintf()
実行が始まる前のある時点で、副作用演算子の が++
実行されます。つまり、2 番目printf()
が発生する前に、最初の演算子i
の結果として が増分されます。ちなみに、これは副作用のタイミングに関して標準が保証している数少ないものの 1 つです。++
printf()
コード内で式が*p++
評価されると、 と評価されます'H'
。しかし、次のようになるまでに:
printf ("%c", *p)
厄介な副作用が発生しました。p
が増分されました。 おっと! は を指しているの'H'
ではなく、 の 1 文字前'H'
、つまり を指しています'e'
。これで、コックニー風の出力が説明できます。
ello
"Hello"
そのため、他の回答では役に立つ(そして正確な)提案が相次いでいます。コックニー発音ではなく受容発音を印刷するには、次のようなものが必要です。
while (*p)
printf ("%c", *p++);
それはここまでです。残りはどうですか? 次の意味について質問します。
*ptr++
*++ptr
++*ptr
1 番目については先ほど説明しましたので、2 番目について見てみましょう*++ptr
。
先ほどの説明で、接尾辞の増分p++
には一定の優先順位、価値、そして副作用プレフィックスの増分++p
は同じ副作用後置演算子と同様に、オペランドを1増やします。ただし、優先順位そして別の価値。
プレフィックスインクリメントの優先順位はポストフィックスよりも低く、優先順位は15です。言い換えると、間接参照演算子と同じ優先順位です*
。次のような式では、
*++ptr
重要なのは優先順位ではありません。2つの演算子の優先順位は同じです。連想性 kicks in. The prefix increment and the indirection operator have right-left associativity. Because of that associativity, the operand ptr
is going to be grouped with the rightmost operator ++
before the operator more to the left, *
. In other words, the expression is going to be grouped *(++ptr)
. So, as with *ptr++
but for a different reason, here too the *
part is going to be applied to the value of the ++ptr
part.
So what is that value? The value of the prefix increment expression is the value of the operand after the increment. This makes it a very different beast from the postfix increment operator. Let's say you have:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
The output will be:
8
8
... different from what we saw with the postfix operator. Similarly, if you have:
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++
the output will be:
H e e l // good dog
Do you see why?
Now we get to the third expression you asked about, ++*ptr
. That's the trickiest of the lot, actually. Both operators have the same precedence, and right-left associativity. This means the expression will be grouped ++(*ptr)
. The ++
part will be applied to the value of the *ptr
part.
So if we have:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
the surprisingly egotistical output is going to be:
I
What?! Okay, so the *p
part is going to evaluate to 'H'
. Then the ++
comes into play, at which point, it's going to be applied to the 'H'
, not to the pointer at all! What happens when you add 1 to 'H'
? You get 1 plus the ASCII value of 'H'
, 72; you get 73. Represent that as a char
, and you get the char
with the ASCII value of 73: 'I'
.
That takes care of the three expressions you asked about in your question. Here is another, mentioned in the first comment to your question:
(*ptr)++
That one is interesting too. If you have:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
it will give you this enthusiastic output:
HI
What's going on? Again, it's a matter of precedence, expression value, and side effects. Because of the parentheses, the *p
part is treated as a primary expression. Primary expressions trump everything else; they get evaluated first. And *p
, as you know, evaluates to 'H'
. The rest of the expression, the ++
part, is applied to that value. So, in this case, (*p)++
becomes 'H'++
.
What is the value of 'H'++
? If you said 'I'
, you've forgotten (already!) our discussion of value vs. side effect with postfix increment. Remember, 'H'++
evaluates to the current value of 'H'
. So that first printf()
is going to print 'H'
. Then, as a side effect, that 'H'
is going to be incremented to 'I'
. The second printf()
prints that 'I'
. And you have your cheery greeting.
All right, but in those last two cases, why do I need
char q[] = "Hello";
char* p = q;
Why can't I just have something like
char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
は文字列リテラルであるためです"Hello"
。 を試みると、文字列内の を に++*p
変更しようとすることになり、文字列全体が になります。C では、文字列リテラルは読み取り専用です。文字列リテラルを変更しようとすると、未定義の動作が発生します。 は英語でも undefined ですが、これは単なる偶然です。'H'
'I'
"Iello"
"Iello"
逆に言えば、
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
なぜそうではないのでしょうか? この場合、p
は配列だからです。配列は変更可能な左辺値ではありません。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
そして、他の 3 つと同じくらい楽しい 4 つ目の例をご紹介します。
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
1 番目と 2 番目は、ptr
実際に配列識別子である場合にクラッシュします。3 番目と 4 番目は、ptr
文字列リテラルを指している場合にクラッシュします。
これで終わりです。これですべてクリアになっているといいのですが。皆さんは素晴らしい聴衆でした。私は今週ずっとここにいます。