Intereting Posts
Почему перемещение указателя буфера замедляет fread (язык программирования C)? читать только целые числа из сложной строки в c Почему streamи называются легкими процессами? «Использование знакового целочисленного операнда с двоичным побитовым оператором» – при использовании короткого замыкания без знака как сделать несколько байт чтения / записи HDD / USB в c Что вызывает ошибку «неопределенная ссылка на (некоторая функция)»? Необработанные кадры H264 в контейнере mpegts с использованием libavcodec Распределить структуру с помощью MPI_Allgather Что делать, если я не пишу по умолчанию в случае коммутатора? R с C / C ++ и openMP: как сделать файл «Makevars» в папке «mypackage / src /»? Переполнение буфера не работает atoi – стандартная функция. Но это не так. Зачем? Как написать собственный код генератора для gcc? Правильный способ объединения двух двойных ссылок Как разрешить ошибку компоновщика в компиляторе C ++

Смутно о доступе к элементам структуры через указатель

Я новичок в C, и меня смущают результаты, которые я получаю при ссылке на элемент структуры с помощью указателя. Для примера см. Следующий код. Что происходит, когда я упоминаю номер tst-> в первый раз? Какую основную вещь я пропущу здесь?

#include  #include  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 

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

    и все будет так, как вы ожидаете.

    Чтобы расширить ответ BlodBath, подумайте о том, что происходит в памяти, когда вы это делаете.

    Когда вы вводите основную процедуру, создается новая автоматическая тестовая структура – в стеке, так как она автоматическая. Таким образом, ваш стек выглядит примерно так:

         |  обратный адрес для основного |  будет использоваться внизу
         |  argc |  скопирован в стек из среды
         |  Адрес argv |  скопирован в стек из среды
     -> |  test.number |  по определению Test test;
    

    с -> указывает указатель стека на последний использованный элемент стека.

    Теперь вы вызываете Test_new() , и он обновляет стек следующим образом:

         |  обратный адрес для основного |  будет использоваться внизу
         |  argc |  скопирован в стек из среды
         |  Адрес argv |  скопирован в стек из среды
         |  test.number |  по определению Test test;
         |  return addr для Test_new |  используется для возврата внизу
         |  копия test.number |  скопирован в стек, потому что C ВСЕГДА использует вызов по значению
     -> |  10 |  скопирован в стек
    

    Когда вы вернетесь &t , какой адрес вы получаете? Ответ: адрес данных на экране. НО ТОГДА вы возвращаетесь, указатель стека уменьшается. Когда вы вызываете printf , эти слова в стеке повторно используются, но ваш адрес по-прежнему подходит к ним. Бывает, что число в этом месте в стеке, которое интерпретируется как адрес, указывает на значение 4206602, но это чистый шанс; на самом деле, это было своего рода неудачей, так как удача была бы чем-то, что вызвало ошибку сегментации, давая вам знать, что что-то действительно сломалось.

    Проблема в том, что вы не передаете ссылку в Test_New , вы передаете значение. Затем вы возвращаете местоположение памяти локальной переменной . Рассмотрим этот код, который демонстрирует вашу проблему:

     #include  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 

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

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

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

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

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

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

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

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

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