簡體   English   中英

對通過指針訪問struct成員感到困惑

[英]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時會發生什么。 創建一個名為tTest結構,並將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.

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