67

C でのポインタの動作を理解しようとして、次のことに少し驚きました (以下のコード例)。

#include <stdio.h>

void add_one_v1(int *our_var_ptr)
{
    *our_var_ptr = *our_var_ptr +1;
}

void add_one_v2(int *our_var_ptr)
{
    *our_var_ptr++;
}

int main()
{
    int testvar;

    testvar = 63;
    add_one_v1(&(testvar));         /* Try first version of the function */
    printf("%d\n", testvar);        /* Prints out 64                     */
    printf("@ %p\n\n", &(testvar));

    testvar = 63;
    add_one_v2(&(testvar));         /* Try first version of the function */
    printf("%d\n", testvar);        /* Prints 63 ?                       */
    printf("@ %p\n", &(testvar));   /* Address remains identical         */
}

出力:

64
@ 0xbf84c6b0

63
@ 0xbf84c6b0

明らかに と同じではないので*our_var_ptr++、2 番目の関数 ( ) のステートメントは正確に何をしますか?add_one_v2*our_var_ptr = *our_var_ptr +1

4

13 に答える 13

85

これは、C と C++ をとても楽しくする小さな落とし穴の 1 つです。脳を曲げたい場合は、次のことを考えてください。

while (*dst++ = *src++) ;

文字列のコピーです。ポインターは、値がゼロの文字がコピーされるまでインクリメントされ続けます。このトリックが機能する理由を理解したら、ポインターに対する ++ の機能を再び忘れることはありません。

PS 演算子の順序はいつでも括弧でオーバーライドできます。以下は、ポインター自体ではなく、ポイントされている値をインクリメントします。

(*our_var_ptr)++;
于 2009-05-13T19:09:12.220 に答える
51

演算子の優先順位規則と++後置演算子であるという事実により、add_one_v2()はポインターを逆参照しますが、実際にはポインター自体++に適用されています。ただし、C は常に値渡しを使用することに注意してください。つまり、ポインターのローカル コピーをインクリメントしているため、そのアドレスに格納されている値には何の影響もありません。add_one_v2()

テストとして、add_one_v2()次のコードに置き換えて、出力がどのように影響を受けるかを確認します。

void add_one_v2(int *our_var_ptr)
{
    (*our_var_ptr)++;  // Now stores 64
}

void add_one_v2(int *our_var_ptr)
{
    *(our_var_ptr++);  // Increments the pointer, but this is a local
                       // copy of the pointer, so it doesn't do anything.
}
于 2009-05-13T19:05:39.840 に答える
44

わかった、

*our_var_ptr++;

それはこのように動作します:

  1. our_var_ptr逆参照が最初に行われ、 (63 を含む) で示されるメモリ位置が与えられます。
  2. 次に式が評価され、63 の結果は 63 のままです。
  3. 結果は破棄されます (何もしていません)。
  4. our_var_ptrその後、評価後にインクリメントされます。ポインターが指している場所ではなく、ポインターが指している場所を変更しています。

事実上、これを行うのと同じです:

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 

わかる?Mark Ransom の回答には、実際に結果を使用していることを除いて、これの良い例があります。

于 2009-05-13T19:50:30.607 に答える
8

ここでは多くの混乱が生じるため、何が起こるかを明確にする (または少なくとも明確にする) 修正されたテスト プログラムを次に示します。

#include <stdio.h>

void add_one_v1(int *p){
  printf("v1: pre:   p = %p\n",p);
  printf("v1: pre:  *p = %d\n",*p);
    *p = *p + 1;
  printf("v1: post:  p = %p\n",p);
  printf("v1: post: *p = %d\n",*p);
}

void add_one_v2(int *p)
{
  printf("v2: pre:   p = %p\n",p);
  printf("v2: pre:  *p = %d\n",*p);
    int q = *p++;
  printf("v2: post:   p = %p\n",p);
  printf("v2: post:  *p = %d\n",*p);
  printf("v2: post:   q = %d\n",q);
}

int main()
{
  int ary[2] = {63, -63};
  int *ptr = ary;

    add_one_v1(ptr);         
    printf("@ %p\n", ptr);
    printf("%d\n", *(ptr));  
    printf("%d\n\n", *(ptr+1)); 

    add_one_v2(ptr);
    printf("@ %p\n", ptr);
    printf("%d\n", *ptr);
    printf("%d\n", *(ptr+1)); 
}

結果の出力で:

v1: pre:   p = 0xbfffecb4
v1: pre:  *p = 63
v1: post:  p = 0xbfffecb4
v1: post: *p = 64
@ 0xbfffecb4
64
-63

v2: pre:   p = 0xbfffecb4
v2: pre:  *p = 64
v2: post:  p = 0xbfffecb8
v2: post: *p = -63
v2: post:  q = 64

@ 0xbfffecb4
64
-63

4 つの注意事項:

  1. ポインターのローカル コピーへの変更は、呼び出し元のポインターには反映されません。
  2. ローカル ポインターのターゲットへの変更は、呼び出しポインターのターゲットに影響します (少なくともターゲット ポインターが更新されるまで)。
  3. で指されている値add_one_v2はインクリメントされておらず、次の値でもありませんが、ポインターは
  4. ポインターのインクリメントは、逆参照のadd_one_v2に発生します

なんで?

  • (逆参照または乗算として)++よりも強くバインドされるため、インクリメントはポインターに適用され、ポインターが指すものには適用されません。*add_one_v2
  • ポストインクリメントは項の評価に発生するため、逆参照は配列の最初の値 (要素 0) を取得します。
于 2009-05-13T20:13:59.727 に答える
7

他の人が指摘しているように、演算子の優先順位により、v2関数の式は。として表示され*(our_var_ptr++)ます。

ただし、これはポストインクリメント演算子であるため、ポインターをインクリメントしてから逆参照すると言うのは正しくありません。これが本当なら、次のメモリ位置に値を返すので、出力として63を取得することはないと思います。実際、操作の論理的な順序は次のとおりです。

  1. ポインタの現在の値を保存します
  2. ポインタをインクリメントします
  3. 手順1で保存したポインタ値を間接参照します

htwが説明したように、ポインターの値は関数に値によって渡されているため、ポインターの値の変更は表示されません。

于 2009-05-13T19:13:12.480 に答える
3

our_var_ptrは、メモリへのポインタです。つまり、データが保存されているメモリセルです。(この場合、intのバイナリ形式で4バイト)。

* our_var_ptrは、逆参照されたポインターです。ポインターが「指している」場所に移動します。

++は値をインクリメントします。

それで。*our_var_ptr = *our_var_ptr+1ポインターを逆参照し、その場所の値に1を追加します。

次に、演算子の優先順位を追加します。これを次のように読み(*our_var_ptr) = (*our_var_ptr)+1ます。逆参照が最初に発生することがわかるので、値を取得して増分します。

他の例では、++演算子は*よりも優先順位が低いため、渡したポインターを取得し、ポインターを追加して(つまり、現在はガベージを指します)、戻ります。(値は常にCの値で渡されるため、関数が元のtestvarポインターを返す場合は、関数内のポインターのみを変更したことに注意してください)。

私のアドバイスは、間接参照(または他の何か)を使用するときは、括弧を使用して決定を明確にします。優先順位の規則を覚えようとしないでください。ある日、わずかに異なる別の言語を使用することになり、混乱することになります。または、古くて、どちらが優先順位が高いかを忘れてしまいます(*と->で行うように)。

于 2009-05-13T19:13:36.190 に答える
0

'++' 演算子は '*' 演算子よりも優先されます。これは、ポインター アドレスが逆参照される前にインクリメントされることを意味します。

ただし、「+」演算子は「*」よりも優先順位が低くなります。

于 2009-05-13T19:04:09.107 に答える
-1

ポインターは値によって渡されるため、ローカル コピーのみがインクリメントされます。本当にポインタをインクリメントしたい場合は、次のように参照渡しする必要があります。

void inc_value_and_ptr(int **ptr)
{
   (**ptr)++;
   (*ptr)++;
}
于 2012-08-17T14:41:55.510 に答える