我是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
答案 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)
测试 是一个局部变量。您正在尝试返回本地变量的地址。一旦函数存在,局部变量就会被破坏,内存将被释放,这意味着编译器可以自由地将一些其他值放在保存局部变量的位置。
在您尝试第二次访问该值的程序中,内存位置可能已分配给其他变量或进程。因此,你得到了错误的输出。
更好的选择是通过引用而不是通过值传递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实例分配空间,例如,如果您需要保存现有的一个状态,以便以后可以恢复...或者出于任何原因。
除非,当然有一些理由说你必须把它保留在当地......但我真的想不到一个。