簡體   English   中英

取消引用指針的后增量?

[英]Post-increment on a dereferenced pointer?

試圖理解 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

第二個函數 ( add_one_v2 ) 中的*our_var_ptr++語句究竟做了什么,因為它顯然與*our_var_ptr = *our_var_ptr +1

這是讓 C 和 C++ 如此有趣的小問題之一。 如果你想彎曲你的大腦,想出這個:

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

這是一個字符串副本。 指針不斷增加,直到復制了一個值為 0 的字符。 一旦你知道這個技巧為什么有效,你就永遠不會忘記 ++ 是如何在指針上工作的。

PS您總是可以用括號覆蓋運算符順序。 以下將增加指向的值,而不是指針本身:

(*our_var_ptr)++;

由於運算符優先級規則和++是后綴運算符這一事實, 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.
}

好的,

*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 的回答有一個很好的例子,除了他實際上使用了結果。

這里有很多混亂,所以這是一個修改后的測試程序,以使發生的事情變得清晰(或至少清晰er ):

#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

需要注意的四點:

  1. 對指針本地副本的更改不會反映在調用指針中。
  2. 對本地指針目標的更改確實會影響調用指針的目標(至少在目標指針更新之前)
  3. add_one_v2中指向的值遞增,后面的值也不遞增,但指針是
  4. add_one_v2中指針的增量發生取消引用之后

為什么?

  • 因為++*綁定更緊密(作為取消引用或乘法),所以add_one_v2中的增量適用於指針,而不是它指向的內容。
  • 增量發生對術語的評估之后,因此取消引用獲取數組中的第一個值(元素 0)。

正如其他人指出的那樣,運算符優先級導致 v2 函數中的表達式被視為*(our_var_ptr++)

但是,由於這是一個后自增運算符,因此說它先遞增指針然后取消引用它並不完全正確。 如果這是真的,我認為您不會得到 63 作為輸出,因為它將返回下一個內存位置中的值。 實際上,我認為操作的邏輯順序是:

  1. 保存指針的當前值
  2. 增加指針
  3. 取消引用步驟 1 中保存的指針值

正如 htw 所解釋的,您沒有看到指針值的變化,因為它是按值傳遞給函數的。

如果您不使用括號來指定操作順序,則前綴和后綴增量都優先於引用和取消引用。 但是,前綴遞增和后綴遞增是不同的操作。 在 ++x 中,運算符獲取對變量的引用,將其加一並按值返回。 在 x++ 中,運算符遞增變量,但返回其舊值。 它們的行為有點像這樣(想象它們在你的類中被聲明為方法):

//prefix increment (++x)
auto operator++()
{
    (*this) = (*this) + 1;
    return (*this);
}

//postfix increment (x++)
auto operator++(int) //unfortunately, the "int" is how they differentiate
{
    auto temp = (*this);
    (*this) = (*this) + 1; //same as ++(*this);
    return temp;
}

(請注意,后綴增量中包含一個副本,因此效率較低。這就是為什么您應該在循環中更喜歡 ++i 而不是 i++ 的原因,即使現在大多數編譯器都會自動為您執行此操作。)

如您所見,后綴增量首先被處理,但是,由於它的行為方式,您將取消引用指針的先前值。

這是一個例子:

char * x = {'a', 'c'};
char   y = *x++; //same as *(x++);
char   z = *x;

在第二行中,指針 x 將在取消引用之前遞增,但取消引用將發生在 x 的舊值(這是后綴遞增返回的地址)上。 所以 y 將用“a”初始化,z 用“c”初始化。 但如果你這樣做:

char * x = {'a', 'c'};
char   y = (*x)++;
char   z = *x;

在這里,x 將被取消引用,它所指向的('a') 將遞增(到 'b')。 由於后綴增量返回舊值,y 仍將使用 'a' 進行初始化。 由於指針沒有改變,z 將被初始化為新值'b'。

現在讓我們檢查前綴情況:

char * x = {'a', 'c'};
char   y = *++x; //same as *(++x)
char   z = *x;

在這里,取消引用將發生在 x 的增量值上(由前綴增量運算符立即返回),因此 y 和 z 都將使用 'c' 進行初始化。 要獲得不同的行為,您可以更改運算符的順序:

char * x = {'a', 'c'};
char   y = ++*x; //same as ++(*x)
char   z = *x;

在這里,您確保您首先增加 x 的內容並且指針的值永遠不會改變,因此 y 和 z 將被分配 'b'。 strcpy函數中(在其他答案中提到),增量也是首先完成的:

char * strcpy(char * dst, char * src)
{
    char * aux = dst;
    while(*dst++ = *src++);
    return aux;
}

在每次迭代中,首先處理 src++,作為后綴增量,它返回 src 的舊值。 然后, src 的舊值(它是一個指針)被取消引用以分配給賦值運算符左側的任何內容。 然后增加 dst 並取消引用它的舊值以成為左值並接收舊的 src 值。 這就是為什么 dst[0] = src[0]、dst[1] = src[1] 等,直到 *dst 被賦值為 0,從而打破循環。

附錄:

此答案中的所有代碼均使用 C 語言進行了測試。 在 C++ 中,您可能無法對指針進行列表初始化。 因此,如果要在 C++ 中測試示例,則應先初始化一個數組,然后將其降級為指針:

char w[] = {'a', 'c'};
char * x = w;
char   y = *x++; //or the other cases
char   z = *x;

our_var_ptr 是指向某些內存的指針。 即它是存儲數據的存儲單元。 (在這種情況下,4 個字節的二進制格式為 int)。

*our_var_ptr 是取消引用的指針——它指向指針“指向”的位置。

++ 增加一個值。

所以。 *our_var_ptr = *our_var_ptr+1取消引用指針並將該位置的值加一。

現在添加運算符優先級 - 將其讀取為(*our_var_ptr) = (*our_var_ptr)+1並且您會看到取消引用首先發生,因此您獲取該值並遞增它。

在您的另一個示例中,++ 運算符的優先級低於 *,因此它獲取您傳入的指針,向其添加一個(因此它現在指向垃圾),然后返回。 (記住值在 C 中總是按值傳遞的,所以當函數返回原始 testvar 指針時保持不變,您只更改了函數內部的指針)。

我的建議是,在使用取消引用(或其他任何方法)時,請使用括號來明確您的決定。 不要試圖記住優先規則,因為有一天你只會使用另一種語言,它們會略有不同,你會感到困惑。 或者老了,最終忘記了哪個具有更高的優先級(就像我對 * 和 -> 所做的那樣)。

我會嘗試從一個不同的角度來回答這個問題... 第 1 步讓我們看看運算符和操作數:在這種情況下它是操作數,並且您有兩個運算符,在這種情況下 * 用於取消引用和 ++為增量。 具有更高優先級的第 2 步 ++ 的優先級高於 * 第 3 步 ++ 在哪里,它在右邊,這意味着POST增量 在這種情況下,編譯器會在完成后記下“心理筆記”來執行增量與所有其他運算符...請注意,如果它是 *++p 那么它將在此之前執行,因此在這種情況下,它等同於獲取處理器的兩個寄存器,一個將保存取消引用 *p 的值和另一個將保存增加的 p++ 的值,在這種情況下有兩個原因是 POST 活動......這就是在這種情況下它很棘手的地方,而且看起來很矛盾。 人們會期望 ++ 優先於 *,它確實如此,只是 POST 意味着它將僅在所有其他操作數之后應用,在下一個 ';' 之前令牌...

來自 K&R,第 105 頁:“*t++ 的值是 t 在 t 遞增之前指向的字符”。

    uint32_t* test;
    test = &__STACK_TOP;


    for (i = 0; i < 10; i++) {
        *test++ = 0x5A5A5A5A;
    }

    //same as above

    for (i = 0; i < 10; i++) {
        *test = 0x5A5A5A5A;
        test++;
    }

因為 test 是一個指針,所以 test++ (這是沒有取消引用它)將增加指針(它增加 test 的值,它恰好是所指向的(目標)地址)。 因為目標是 uint32_t 類型,test++ 將增加 4 個字節,如果目標是這種類型的數組,那么 test 現在將指向下一個元素。 在進行此類操作時,有時您必須先轉換指針才能獲得所需的內存偏移量。

        ((unsigned char*) test)++;

這只會將地址增加 1 個字節;)

圖片大約值一千字(給或取一百萬左右)......符號可以是圖片(反之亦然。)

(optimized data consumption) yet still want “(mostly) lossless” encoding, vector images/pictures/illustrations/demos are paramount.所以⸺對於我們這些尋求tl;dr (優化數據消耗)但仍然想要“(大部分)無損”編碼的人來說,矢量圖/圖片/插圖/演示是最重要的。

換句話說,請忽略我的最后兩條語句,然后看下面。


有效表格

*a++ ≣ *(a++)
     ≣ (a++)[0] ≣ a++[0]
     ≣ 0[a++]  // Don't you dare use this (“educational purposes only”)
     // These produce equivalent (side) effects;
       ≡ val=*a,++a,val
       ≡ ptr=a,++a,*ptr
       ≡ *(ptr=a,++a,ptr)

*++a ≣ *(++a)
     ≣ *(a+=1)  ≣ *(a=a+1)
     ≣ (++a)[0] ≣ (a+=1)[0] ≣ (a=a+1)[0]  // ()'s are necessary
                ≣ 0[++a]   // 0[a+=1], etc... “educational purposes only”
     // These produce equivalent (side) effects:
       ≡ ++a,*a
       ≡ a+=1,*a
       ≡ a=a+1,*a

++*a ≣ ++(*a)
     ≣ *a+=1
     ≣ *a=*a+1
     ≣ ++a[0] ≣ ++(a[0])
              ≣ ++0[a]  // STAY AWAY

(*a)++  // Note that this does NOT return a pointer;
        // Location `a` points to does not change
        // (i.e. the address `a` is unchanged)
        ≡ val=*a,++*a,val

筆記

cede the target identifier: */ /* 間接/順從運算符必須放棄目標標識符:*/
一個++*;
一個*++;
++a*;

'++' 運算符的優先級高於 '*' 運算符,這意味着指針地址將在取消引用之前遞增。

但是,“+”運算符的優先級低於“*”。

因為指針是按值傳遞的,所以只有本地副本會增加。 如果你真的想增加指針,你必須通過引用傳遞它,如下所示:

void inc_value_and_ptr(int **ptr)
{
   (**ptr)++;
   (*ptr)++;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM