[英]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++;
它是這樣工作的:
our_var_ptr
指示的內存位置(包含 63)。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
需要注意的四點:
add_one_v2
中指向的值不遞增,后面的值也不遞增,但指針是add_one_v2
中指針的增量發生在取消引用之后++
比*
綁定更緊密(作為取消引用或乘法),所以add_one_v2
中的增量適用於指針,而不是它指向的內容。正如其他人指出的那樣,運算符優先級導致 v2 函數中的表達式被視為*(our_var_ptr++)
。
但是,由於這是一個后自增運算符,因此說它先遞增指針然后取消引用它並不完全正確。 如果這是真的,我認為您不會得到 63 作為輸出,因為它將返回下一個內存位置中的值。 實際上,我認為操作的邏輯順序是:
正如 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
'++' 運算符的優先級高於 '*' 運算符,這意味着指針地址將在取消引用之前遞增。
但是,“+”運算符的優先級低於“*”。
因為指針是按值傳遞的,所以只有本地副本會增加。 如果你真的想增加指針,你必須通過引用傳遞它,如下所示:
void inc_value_and_ptr(int **ptr)
{
(**ptr)++;
(*ptr)++;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.