Смущен доступом к членам структуры через указатель - PullRequest
3 голосов
/ 02 января 2009

Я новичок в 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

Ответы [ 8 ]

14 голосов
/ 02 января 2009

Когда вы передаете тест в свою функцию 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);

}
3 голосов
/ 02 января 2009

Независимо от struct, всегда неверно возвращать адрес локальной переменной . Также обычно некорректно помещать адрес локальной переменной в глобальную переменную или сохранять его в объекте, выделенном в куче с malloc. Как правило, если вам нужно вернуть указатель на объект, вам нужно либо попросить кого-то еще предоставить указатель для вас, либо вам нужно выделить пространство с помощью malloc, который вернет указатель. В этом случае часть API для вашей функции должна указывать, кто отвечает за вызов free, когда объект больше не нужен.

1 голос
/ 02 января 2009

Test t , объявленный в Test_New () - локальная переменная. Вы пытаетесь вернуть адрес локальной переменной. Поскольку локальная переменная уничтожается, когда функция существует, память освобождается, то есть компилятор может поместить другое значение в место, где хранилась ваша локальная переменная.

В вашей программе, когда вы пытаетесь получить доступ к значению во второй раз, ячейка памяти могла быть назначена другой переменной или процессу. Следовательно, вы получаете неправильный вывод.

Лучшим вариантом для вас будет передать структуру из main () по ссылке, а не по значению.

1 голос
/ 02 января 2009

Просто чтобы расширить ответ 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, но это чистый шанс; на самом деле, это было своего рода неудача, поскольку удача удача была бы чем-то, что вызвало ошибку сегментации, давая вам знать, что что-то действительно сломалось.

1 голос
/ 02 января 2009

Проблема в том, что вы не передаете ссылку на 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
1 голос
/ 02 января 2009

Вы возвращаете адрес 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);

и все пойдет так, как вы ожидаете.

0 голосов
/ 02 января 2009

Вы можете сделать что-то вроде этого, чтобы сделать это немного проще:

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, например, если вам нужно сохранить существующее состояние одного, чтобы вы могли вернуться позже ... или по любой другой причине.

Если, конечно, есть какая-то причина, почему вы должны держать это локально ... но я действительно не могу думать об этом.

0 голосов
/ 02 января 2009

Вы передали содержимое теста по значению в Test_New. IOW новая копия структуры Test была выделена в стеке, когда вы вызвали Test_New. Это адрес этого теста, который вы возвращаете из функции.

Когда вы используете tst-> number при первом получении значения 10, потому что, хотя этот стек был размотан, никакое другое использование этой памяти не было использовано. Однако, как только этот первый printf был вызван, память стека повторно используется для всего, что ему нужно, но tst все еще указывает на эту память. Следовательно, частое использование tst-> number извлекает то, что printf осталось в этой памяти.

Вместо этого используйте Test & t в сигнатуре функции.

...