对通过指针访问struct成员感到困惑

时间:2009-01-01 22:05:15

标签: c pass-by-value

我是C的新手,我对通过指针引用结构成员时得到的结果感到困惑。有关示例,请参阅以下代码。当我第一次引用tst->数字时发生了什么?我在这里错过了什么基本的东西?

#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

8 个答案:

答案 0 :(得分:14)

当您将测试传递给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);

}

答案 1 :(得分:3)

独立于struct,返回本地变量的地址总是不正确。将局部变量的地址放入全局变量或将其存储在使用malloc的堆上分配的对象中通常也是不正确的。通常,如果需要返回指向对象的指针,则需要让其他人为您提供指针,否则您需要使用malloc分配空间,这将返回指针。在这种情况下,函数的API的一部分必须指定在不再需要对象时谁负责调用free

答案 2 :(得分:1)

您将返回方法t中声明的Test_New地址,而不是您传递给方法的test地址。也就是说,test正在按值传递,您应该将指针传递给它。

所以,这是当你致电Test_New时会发生什么。创建名为Test的新t结构,并将t.number设置为等于test.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);

所有这些都将按照您的预期进行。

答案 3 :(得分:1)

问题是您没有将引用传递给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

答案 4 :(得分:1)

只是为了扩展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;

->表示指向堆栈最后使用元素的堆栈指针。

现在你调用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,但这是纯粹的机会;事实上,这是一种运气不好,因为运气会导致分段错误,让你知道某些东西实际上已被破坏。

答案 5 :(得分:1)

Test_New() 中声明的

测试 是一个局部变量。您正在尝试返回本地变量的地址。一旦函数存在,局部变量就会被破坏,内存将被释放,这意味着编译器可以自由地将一些其他值放在保存局部变量的位置。

在您尝试第二次访问该值的程序中,内存位置可能已分配给其他变量或进程。因此,你得到了错误的输出。

更好的选择是通过引用而不是通过值传递main()中的结构。

答案 6 :(得分:0)

您已将test的内容传递给Test_New。 IOW当您调用Test_New时,已在堆栈上分配了测试结构的新副本。它是从函数返回的此测试的地址。

当您第一次检索到10的值时使用tst-&gt;数字,因为尽管该堆栈已经解开,但是没有对该内存进行其他使用。但是,只要第一个printf被调用,堆栈内存就会被重用,无论它需要什么,但是tst仍然指向那个内存。因此,顺序使用tst-> number会检索该存储器中剩余的printf。

请在功能签名中使用Test&amp; t。

答案 7 :(得分:0)

你可以做这样的事情让它变得更容易一些:

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实例分配空间,例如,如果您需要保存现有的一个状态,以便以后可以恢复...或者出于任何原因。

除非,当然有一些理由说你必须把它保留在当地......但我真的想不到一个。