简体   繁体   English

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

[英]Confused about accessing struct members via a pointer

I'm new to C, and am confused by results I'm getting when referencing a member of a struct via a pointer. 我是C的新手,我对通过指针引用结构成员时得到的结果感到困惑。 See the following code for an example. 有关示例,请参阅以下代码。 What's happening when I reference tst->number the first time? 当我第一次引用tst-> number时发生了什么? What fundamental thing am I missing here? 我在这里错过了什么基本的东西?

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

The output is: 输出是:

Test.number = 10
Test.number = 4206602
Test.number = 4206602

When you pass test into your Test_New function, you are passing it by value and so a local copy is made on the stack for the function scope of your Test_New function. 当您将test传递给Test_New函数时,您将按值传递它,因此在堆栈上为Test_New函数的函数范围创建本地副本。 Since you return the address of the variable, once the function returns the stack is useless but you've returned a pointer to a struct on the old stack! 因为你返回变量的地址,一旦函数返回堆栈是没用的,但是你已经返回了一个指向旧堆栈结构的指针! So you can see that your first call returns the correct value since nothing has overwritten your stack value but the subsequent calls (which all use the stack) overwrite your value and give you erroneous results. 因此,您可以看到您的第一个调用返回正确的值,因为没有任何内容覆盖您的堆栈值,但后续调用(所有使用堆栈)都会覆盖您的值并给您错误的结果。

To do this correctly rewrite your Test_New function to take a pointer and pass the pointer to the struct into the function. 为此,请正确重写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);

}

Independent of struct , it is always incorrect to return the address of a local variable . struct返回局部变量的地址总是不正确的 It is usually also incorrect to put the address of a local variable into a global variable or to store it in an object allocated on the heap with malloc . 将局部变量的地址放入全局变量或将其存储在使用malloc在堆上分配的对象中通常也是不正确的。 Generally if you need to return a pointer to an object, you'll need either to get someone else to provide the pointer for you, or else you'll need to allocate space with malloc , which will return a pointer. 通常,如果需要返回指向对象的指针,则需要让其他人为您提供指针,否则您需要使用malloc分配空间, malloc将返回指针。 In that case, part of the API for your function must specify who is responsible for calling free when the object is no longer needed. 在这种情况下,函数的API的一部分必须指定在不再需要对象时谁负责free调用。

You are returning the address of t as declared in the method Test_New , not the address of test that you passed into the method. 您将返回方法Test_New声明的t的地址,而不是您传递给方法的test地址。 That is, test is being passed by value and you should instead pass a pointer to it. 也就是说, test是通过值传递的,您应该将指针传递给它。

So, here is what happens when you call Test_New . 所以,这就是调用Test_New时会发生什么。 A new Test struct named t is created and t.number is set to be equal to the value of test.number (which you had not initialized). 创建一个名为tTest结构,并将t.number设置为等于test.number的值(您尚未初始化)。 Then you set t.number equal to the parameter number that you passed to the method, and then you return the address of t . 然后将t.number设置t.number等于传递给方法的参数number ,然后返回t的地址。 But t is a local variable and goes out of scope as soon as the method ends. 但是t是局部变量,一旦方法结束就超出范围。 Thus, you are returning a pointer to data that no longer exists and that is why you are ending up with garbage. 因此,您将返回一个指向不再存在的数据的指针,这就是您最终使用垃圾的原因。

Change the declaration of Test_New to Test_New的声明更改为

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

and call it via 并通过它来调用它

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

and all will go as you are expecting. 一切都会按照你的期望去做。

The problem is that you are not passing a reference into Test_New , you are passing a value. 问题是你没有将引用传递给Test_New ,而是传递一个值。 Then, you're returning the memory location of the local variable . 然后,您将返回局部变量的内存位置。 Consider this code which demonstrates your problem: 请考虑以下代码来演示您的问题:

#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;
}

The output of this program on my machine is: 我的机器上的这个程序的输出是:

0xbfffe970
0xbfffe950

Just to extend BlodBath's answer, think about what happens in memory when you do this. 只是为了扩展BlodBath的答案,想想当你这样做时内存中会发生什么。

As you enter your main routine, a new automatic Test struct is created -- on the stack, since it's auto. 当您输入主例程时,会在堆栈上创建一个新的自动Test结构,因为它是自动的。 So your stack looks something like 所以你的堆栈看起来像

| 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 -> indicating the stack pointer to the last used element of the stack. with ->指示堆栈指针指向堆栈的最后一个使用元素。

Now you call Test_new() , and it updates the stack like this: 现在你调用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

When you return &t , which address are you getting? 当你返回&t ,你得到了哪个地址? Answer: the address of the data ON THE STACK. 答案:堆叠数据的地址。 BUT THEN you return, the stack pointer is decremented. 但是,然后返回,堆栈指针递减。 When you call printf , those words on the stack are re-used, but your address is still poiting to them. 当你调用printf ,堆栈上的那些单词会被重复使用,但是你的地址仍在向他们发送。 It happens that what the number in that location in the stack, interpreted as an address, points to has the value 4206602, but that's pure chance; 它发生在堆栈中该位置的数字 (被解释为地址)指向的值为4206602,但这是纯粹的机会; in fact, it was kind of bad luck, as good luck would have been something that caused a segmentation fault, letting you know something was actually broken. 事实上,它是一种坏运气,因为运气会一直的东西,造成分段错误,让你知道的东西居然被打破。

Test t declared in Test_New() is a local variable. Test_New()中声明的测试t是局部变量。 You are trying to return the address of a local variable. 您正在尝试返回本地变量的地址。 As the local variable gets destroyed once the function exists, the memory will be freed meaning, the compiler is free to put some other value in the location where your local variable was kept. 一旦函数存在,局部变量就会被破坏,内存将被释放,这意味着,编译器可以自由地将一些其他值放在保存局部变量的位置。

In your program when you are trying to access the value the second time, the memory location might have got assigned to a different variable or process. 在您的程序中,当您第二次尝试访问该值时,可能已将内存位置分配给其他变量或进程。 Hence you are getting the wrong output. 因此,你得到了错误的输出。

A better option for you will be to pass the structure from main() by reference rather than by value. 对你来说更好的选择是通过引用而不是通过值从main()传递结构。

You've passed the contents of test by value to Test_New. 您已将test的内容传递给Test_New。 IOW a new copy of a Test structure has been allocated on the stack when you called Test_New. IOW当您调用Test_New时,已在堆栈上分配了测试结构的新副本。 It is the address of this Test that you return from the function. 它是从函数返回的此测试的地址。

When you use tst->number the first time the value of 10 is retrieved because although that stack has be unwound no other use of that memory has been made. 当你第一次使用tst-> number时,检索到10的值,因为虽然已经解开了该堆栈但是没有使用该内存。 However as soon as that first printf has been called the stack memory is reused for whatever it needs, but tst is still pointing to that memory. 但是,只要第一个printf被调用,堆栈内存就会被重用,无论它需要什么,但是tst仍然指向那个内存。 Hence subsquent uses of tst->number retrieve whatever printf left there in that memory. 因此,tst-> number的后续使用将检索该内存中剩余的printf。

Use Test &t in the function signature instead. 请改用函数签名中的Test&t。

You could do something like this to make it a little easier: 你可以做这样的事情,使它更容易一些:

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

... As you can see, its easy to allocate room for several completely different instances of test_t, for instance if you need to save the existing state of one so you can revert later .. or for whatever reason. ...正如您所看到的,它很容易为几个完全不同的test_t实例分配空间,例如,如果您需要保存现有状态,以便以后可以还原......或者出于任何原因。

Unless, of course there is some reason why you must keep it local .. but I really can't think of one. 除非,当然有一些原因你必须把它保持在当地..但我真的想不到一个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM