[英]Confused about accessing struct members via a pointer
我是C的新手,我對通過指針引用結構成員時得到的結果感到困惑。 有關示例,請參閱以下代碼。 當我第一次引用tst-> number時發生了什么? 我在這里錯過了什么基本的東西?
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int number;
} Test;
Test* Test_New(Test t,int number) {
t.number = number;
return &t;
}
int main(int argc, char** argv) {
Test test;
Test *tst = Test_New(test,10);
printf("Test.number = %d\n",tst->number);
printf("Test.number = %d\n",tst->number);
printf("Test.number = %d\n",tst->number);
}
輸出是:
Test.number = 10
Test.number = 4206602
Test.number = 4206602
當您將test傳遞給Test_New函數時,您將按值傳遞它,因此在堆棧上為Test_New函數的函數范圍創建本地副本。 因為你返回變量的地址,一旦函數返回堆棧是沒用的,但是你已經返回了一個指向舊堆棧結構的指針! 因此,您可以看到您的第一個調用返回正確的值,因為沒有任何內容覆蓋您的堆棧值,但后續調用(所有使用堆棧)都會覆蓋您的值並給您錯誤的結果。
為此,請正確重寫Test_New函數以獲取指針並將指針傳遞給函數。
Test* Test_New(Test * t,int number) {
t->number = number;
return t;
}
int main(int argc, char ** argv) {
Test test;
Test * tst = Test_New(&test,10);
printf("Test.number = %d\n",tst->number);
printf("Test.number = %d\n",tst->number);
printf("Test.number = %d\n",tst->number);
}
與struct
, 返回局部變量的地址總是不正確的 。 將局部變量的地址放入全局變量或將其存儲在使用malloc
在堆上分配的對象中通常也是不正確的。 通常,如果需要返回指向對象的指針,則需要讓其他人為您提供指針,否則您需要使用malloc
分配空間, malloc
將返回指針。 在這種情況下,函數的API的一部分必須指定在不再需要對象時誰負責free
調用。
您將返回方法Test_New
聲明的t
的地址,而不是您傳遞給方法的test
地址。 也就是說, test
是通過值傳遞的,您應該將指針傳遞給它。
所以,這就是調用Test_New
時會發生什么。 創建一個名為t
新Test
結構,並將t.number
設置為等於test.number
的值(您尚未初始化)。 然后將t.number
設置t.number
等於傳遞給方法的參數number
,然后返回t
的地址。 但是t
是局部變量,一旦方法結束就超出范圍。 因此,您將返回一個指向不再存在的數據的指針,這就是您最終使用垃圾的原因。
將Test_New
的聲明更改為
Test* Test_New(Test* t,int number) {
t->number = number;
return t;
}
並通過它來調用它
Test *tst = Test_New(&test,10);
一切都會按照你的期望去做。
問題是你沒有將引用傳遞給Test_New
,而是傳遞一個值。 然后,您將返回局部變量的內存位置。 請考慮以下代碼來演示您的問題:
#include <stdio.h>
typedef struct {
} Test;
void print_pass_by_value_memory(Test t) {
printf("%p\n", &t);
}
int main(int argc, char** argv) {
Test test;
printf("%p\n", &test);
print_pass_by_value_memory(test);
return 0;
}
我的機器上的這個程序的輸出是:
0xbfffe970
0xbfffe950
只是為了擴展BlodBath的答案,想想當你這樣做時內存中會發生什么。
當您輸入主例程時,會在堆棧上創建一個新的自動Test結構,因為它是自動的。 所以你的堆棧看起來像
| return address for main | will be used at bottom | argc | copied onto stack from environment | argv address | copied onto stack from environment -> | test.number | created by definition Test test;
with ->
指示堆棧指針指向堆棧的最后一個使用元素。
現在你調用Test_new()
,它會像這樣更新堆棧:
| return address for main | will be used at bottom | argc | copied onto stack from environment | argv address | copied onto stack from environment | test.number | created by definition Test test; | return addr for Test_new| used to return at bottom | copy of test.number | copied into the stack because C ALWAYS uses call by value -> | 10 | copied onto stack
當你返回&t
,你得到了哪個地址? 答案:堆疊數據的地址。 但是,然后返回,堆棧指針遞減。 當你調用printf
,堆棧上的那些單詞會被重復使用,但是你的地址仍在向他們發送。 它發生在堆棧中該位置的數字 (被解釋為地址)指向的值為4206602,但這是純粹的機會; 事實上,它是一種壞運氣,因為運氣好會一直的東西,造成分段錯誤,讓你知道的東西居然被打破。
Test_New()中聲明的測試t是局部變量。 您正在嘗試返回本地變量的地址。 一旦函數存在,局部變量就會被破壞,內存將被釋放,這意味着,編譯器可以自由地將一些其他值放在保存局部變量的位置。
在您的程序中,當您第二次嘗試訪問該值時,可能已將內存位置分配給其他變量或進程。 因此,你得到了錯誤的輸出。
對你來說更好的選擇是通過引用而不是通過值從main()傳遞結構。
您已將test的內容傳遞給Test_New。 IOW當您調用Test_New時,已在堆棧上分配了測試結構的新副本。 它是從函數返回的此測試的地址。
當你第一次使用tst-> number時,檢索到10的值,因為雖然已經解開了該堆棧但是沒有使用該內存。 但是,只要第一個printf被調用,堆棧內存就會被重用,無論它需要什么,但是tst仍然指向那個內存。 因此,tst-> number的后續使用將檢索該內存中剩余的printf。
請改用函數簽名中的Test&t。
你可以做這樣的事情,使它更容易一些:
typedef struct test {
int number;
} test_t;
test_t * Test_New(int num)
{
struct test *ptr;
ptr = (void *) malloc(sizeof(struct test));
if (! ptr) {
printf("Out of memory!\n");
return (void *) NULL;
}
ptr->number = num;
return ptr;
}
void cleanup(test_t *ptr)
{
if (ptr)
free(ptr);
}
....
int main(void)
{
test_t *test, *test1, *test2;
test = Test_New(10);
test1 = Test_New(20);
test2 = Test_new(30);
printf(
"Test (number) = %d\n"
"Test1 (number) = %d\n"
"Test2 (number) = %d\n",
test->number, test1->number, test2->number);
....
cleanup(test1);
cleanup(test2);
cleanup(test3);
return 0;
}
...正如您所看到的,它很容易為幾個完全不同的test_t實例分配空間,例如,如果您需要保存現有狀態,以便以后可以還原......或者出於任何原因。
除非,當然有一些原因你必須把它保持在當地..但我真的想不到一個。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.