Запутался в доступе к элементам структуры через указатель
Я новичок в 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 ответов
Когда вы проходите тестирование в своей функции 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);
}
Независим от struct
всегда неверно возвращать адрес локальной переменной. Также обычно некорректно помещать адрес локальной переменной в глобальную переменную или сохранять его в объекте, выделенном в куче с помощью malloc
, Как правило, если вам нужно вернуть указатель на объект, вам нужно либо попросить кого-то, кто предоставит указатель для вас, либо вам нужно выделить место с помощью malloc
, который будет возвращать указатель. В этом случае часть API для вашей функции должна указывать, кто отвечает за вызов free
когда объект больше не нужен.
Вы возвращаете адрес 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);
и все пойдет так, как вы ожидаете.
Проблема в том, что вы не передаете ссылку в 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
Test t, объявленный в Test_New(), является локальной переменной. Вы пытаетесь вернуть адрес локальной переменной. Поскольку локальная переменная уничтожается после того, как функция существует, память освобождается, то есть компилятор может поместить другое значение в место, где хранилась ваша локальная переменная.
В вашей программе, когда вы пытаетесь получить доступ к значению во второй раз, ячейка памяти могла быть назначена другой переменной или процессу. Следовательно, вы получаете неправильный вывод.
Лучшим вариантом для вас будет передать структуру из main() по ссылке, а не по значению.
Просто чтобы расширить ответ BlodBath, подумайте о том, что происходит в памяти, когда вы делаете это.
Когда вы вводите свою основную подпрограмму, создается новая автоматическая структура Test - в стеке, так как она автоматическая. Так что ваш стек выглядит примерно так
| обратный адрес для основного | будет использоваться в нижней части | argc | скопированы в стек из среды | адрес argv | скопированы в стек из среды -> | test.number | созданный по определению Test test;
с ->
указание указателя стека на последний использованный элемент стека.
Теперь звонишь Test_new()
и он обновляет стек следующим образом:
| обратный адрес для основного | будет использоваться в нижней части | argc | скопированы в стек из среды | адрес argv | скопированы в стек из среды | test.number | созданный по определению Test test; | вернуть адрес для Test_new| раньше возвращался внизу | копия test.number | скопированы в стек, потому что C ВСЕГДА использует вызов по значению -> | 10 | скопированы в стек
Когда ты вернешься &t
какой адрес вы получаете? Ответ: адрес данных НА СТЕКЕ. НО после того, как вы вернетесь, указатель стека уменьшается. Когда вы звоните printf
эти слова в стеке используются повторно, но ваш адрес все еще находится на них. Бывает, что то, на что указывает число в этом месте в стеке, интерпретируемое как адрес, имеет значение 4206602, но это чистый шанс; на самом деле, это было своего рода неудача, поскольку удача была бы чем-то, что вызвало ошибку сегментации, давая вам знать, что что-то действительно сломалось.
Вы можете сделать что-то вроде этого, чтобы сделать это немного проще:
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, например, если вам нужно сохранить существующее состояние одного, чтобы вы могли вернуться позже... или по любой другой причине.
Если, конечно, есть какая-то причина, почему вы должны держать это локально... но я действительно не могу думать об этом.
Вы передали содержимое теста по значению в Test_New. IOW новая копия структуры Test была выделена в стеке, когда вы вызвали Test_New. Это адрес этого теста, который вы возвращаете из функции.
Когда вы используете tst->number в первый раз, извлекается значение 10, потому что, хотя этот стек был размотан, никакое другое использование этой памяти не было сделано. Однако, как только этот первый printf был вызван, память стека используется повторно для всего, что ему нужно, но tst все еще указывает на эту память. Следовательно, последующее использование tst->number возвращает то, что printf осталось в этой памяти.
Вместо этого используйте Test &t в сигнатуре функции.