3

我是 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
4

8 回答 8

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);

}
于 2009-01-01T22:18:22.733 回答
3

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

于 2009-01-02T05:01:44.900 回答
1

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

所以,当你调用Test_New. 创建了一个名为的新Test结构并将其设置为等于(您尚未初始化)的值。然后你设置tt.numbertest.numbert.number等于number你传递给方法的参数,然后你返回的地址t。但是t是一个局部变量,一旦方法结束就超出范围。因此,您正在返回一个指向不再存在的数据的指针,这就是您最终得到垃圾的原因。

更改声明Test_New

Test* Test_New(Test* t,int number) {
    t->number = number;
    return t;
}

并通过调用它

Test *tst = Test_New(&test,10);

一切都会如你所愿。

于 2009-01-01T22:15:32.503 回答
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
于 2009-01-01T22:20:34.770 回答
1

只是为了扩展 BlodBath 的答案,想想当你这样做时内存中会发生什么。

当您进入主程序时,会在堆栈上创建一个新的自动测试结构,因为它是自动的。所以你的堆栈看起来像

    | 主要返回地址 | 将在底部使用
    | 氩气 | 从环境复制到堆栈
    | argv 地址 | 从环境复制到堆栈
-> | 测试号 | 按定义创建测试测试;

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

现在您调用Test_new(),它会像这样更新堆栈:

    | 主要返回地址 | 将在底部使用
    | 氩气 | 从环境复制到堆栈
    | argv 地址 | 从环境复制到堆栈
    | 测试号 | 按定义创建测试测试;
    | 返回 Test_new 的地址| 用于返回底部
    | test.number 的副本 | 复制到堆栈中,因为 C 总是使用按值调用
-> | 10 | 复制到堆栈

当您返回&t时,您将获得哪个地址?答:堆栈上数据的地址。但是然后你返回,堆栈指针递减。当您调用 时printf,堆栈上的那些单词会被重新使用,但您的地址仍然指向它们。碰巧,堆栈中该位置的数字(解释为地址)指向的值为 4206602,但这纯属偶然;事实上,这是一种倒霉,因为好运会导致分段错误,让您知道某些东西实际上被破坏了。

于 2009-01-01T22:33:37.133 回答
1

在Test_New()中声明的测试 t是一个局部变量。您正在尝试返回局部变量的地址。一旦函数存在,局部变量就会被销毁,内存将被释放,这意味着编译器可以自由地将其他值放在保存局部变量的位置。

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

对您来说更好的选择是通过引用而不是通过值从 main() 传递结构。

于 2009-01-02T10:21:42.867 回答
0

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

当您第一次使用 tst->number 时,将检索 10 的值,因为尽管该堆栈已展开,但尚未对该内存进行其他使用。然而,一旦第一个 printf 被调用,堆栈内存就会被重新用于它需要的任何东西,但 tst 仍然指向该内存。因此 tst->number 的后续使用会检索 printf 留在该内存中的任何内容。

在函数签名中使用 Test &t 。

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

除非,当然有某些原因必须将其保留在本地.. 但我真的想不出一个。

于 2009-01-02T05:57:22.417 回答