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

时间:2022-12-29 15:54:12

I'm new to C, and am confused by results I'm getting when referencing a member of a struct via a pointer. See the following code for an example. What's happening when I reference tst->number the first time? What fundamental thing am I missing here?

我是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);
}

The output is:

输出是:

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

8 个解决方案

#1


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. 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.

当您将test传递给Test_New函数时,您将按值传递它,因此在堆栈上为Test_New函数的函数范围创建本地副本。因为你返回变量的地址,一旦函数返回堆栈是没用的,但是你已经返回了一个指向旧堆栈结构的指针!因此,您可以看到您的第一个调用返回正确的值,因为没有任何内容覆盖您的堆栈值,但后续调用(都使用堆栈)会覆盖您的值并给您错误的结果。

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

}

#2


Independent of struct, it is always incorrect to return the address of a local variable. 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. 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. 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.

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

#3


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. That is, test is being passed by value and you should instead pass a pointer to it.

您将返回方法Test_New中声明的t的地址,而不是您传递给方法的测试地址。也就是说,测试是通过值传递的,您应该将指针传递给它。

So, here is what happens when you call 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). Then you set t.number equal to the parameter number that you passed to the method, and then you return the address of t. But t is a local variable and goes out of scope as soon as the method ends. Thus, you are returning a pointer to data that no longer exists and that is why you are ending up with garbage.

所以,这是当你调用Test_New时会发生什么。创建一个名为t的新测试结构,并将t.number设置为等于test.number的值(您尚未初始化)。然后将t.number设置为等于传递给方法的参数编号,然后返回t的地址。但是t是局部变量,一旦方法结束就超出范围。因此,您将返回一个指向不再存在的数据的指针,这就是您最终使用垃圾的原因。

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.

一切都会按照你的期望去做。

#4


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. So your stack looks something like

当您输入主例程时,会在堆栈上创建一个新的自动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 -> 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? 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. 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; 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.

当你返回&t时,你得到了哪个地址?答案:堆叠数据的地址。但是,然后返回,堆栈指针递减。当你调用printf时,堆栈中的那些单词会被重复使用,但是你的地址仍在向他们发送。碰巧的是,堆栈中该位置的数字(被解释为地址)指向的值为4206602,但这是纯粹的机会;事实上,这是一种运气不好,因为好运会导致分段错误,让你知道实际上有些东西被打破了。

#5


The problem is that you are not passing a reference into Test_New, you are passing a value. Then, you're returning the memory location of the local variable. Consider this code which demonstrates your problem:

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

The output of this program on my machine is:

我的机器上的这个程序的输出是:

0xbfffe970
0xbfffe950

#6


Test t declared in Test_New() is a local variable. 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.

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

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()传递结构。

#7


You've passed the contents of test by value to Test_New. IOW a new copy of a Test structure has been allocated on the stack when you called Test_New. It is the address of this Test that you return from the function.

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

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. 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. Hence subsquent uses of tst->number retrieve whatever printf left there in that memory.

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

Use Test &t in the function signature instead.

请改用函数签名中的Test&t。

#8


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.

除非,当然有一些原因你必须保持本地..但我真的想不到一个。

#1


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. 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.

当您将test传递给Test_New函数时,您将按值传递它,因此在堆栈上为Test_New函数的函数范围创建本地副本。因为你返回变量的地址,一旦函数返回堆栈是没用的,但是你已经返回了一个指向旧堆栈结构的指针!因此,您可以看到您的第一个调用返回正确的值,因为没有任何内容覆盖您的堆栈值,但后续调用(都使用堆栈)会覆盖您的值并给您错误的结果。

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

}

#2


Independent of struct, it is always incorrect to return the address of a local variable. 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. 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. 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.

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

#3


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. That is, test is being passed by value and you should instead pass a pointer to it.

您将返回方法Test_New中声明的t的地址,而不是您传递给方法的测试地址。也就是说,测试是通过值传递的,您应该将指针传递给它。

So, here is what happens when you call 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). Then you set t.number equal to the parameter number that you passed to the method, and then you return the address of t. But t is a local variable and goes out of scope as soon as the method ends. Thus, you are returning a pointer to data that no longer exists and that is why you are ending up with garbage.

所以,这是当你调用Test_New时会发生什么。创建一个名为t的新测试结构,并将t.number设置为等于test.number的值(您尚未初始化)。然后将t.number设置为等于传递给方法的参数编号,然后返回t的地址。但是t是局部变量,一旦方法结束就超出范围。因此,您将返回一个指向不再存在的数据的指针,这就是您最终使用垃圾的原因。

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.

一切都会按照你的期望去做。

#4


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. So your stack looks something like

当您输入主例程时,会在堆栈上创建一个新的自动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 -> 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? 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. 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; 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.

当你返回&t时,你得到了哪个地址?答案:堆叠数据的地址。但是,然后返回,堆栈指针递减。当你调用printf时,堆栈中的那些单词会被重复使用,但是你的地址仍在向他们发送。碰巧的是,堆栈中该位置的数字(被解释为地址)指向的值为4206602,但这是纯粹的机会;事实上,这是一种运气不好,因为好运会导致分段错误,让你知道实际上有些东西被打破了。

#5


The problem is that you are not passing a reference into Test_New, you are passing a value. Then, you're returning the memory location of the local variable. Consider this code which demonstrates your problem:

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

The output of this program on my machine is:

我的机器上的这个程序的输出是:

0xbfffe970
0xbfffe950

#6


Test t declared in Test_New() is a local variable. 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.

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

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()传递结构。

#7


You've passed the contents of test by value to Test_New. IOW a new copy of a Test structure has been allocated on the stack when you called Test_New. It is the address of this Test that you return from the function.

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

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. 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. Hence subsquent uses of tst->number retrieve whatever printf left there in that memory.

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

Use Test &t in the function signature instead.

请改用函数签名中的Test&t。

#8


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.

除非,当然有一些原因你必须保持本地..但我真的想不到一个。