C программирование malloc и NULL - PullRequest
0 голосов
/ 29 сентября 2018

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

Когда я объявляю головной узел, есть ли разница между следующими утверждениями?Если так, то какой путь правильный?Я чувствую, что первый правильный путь:

    struct Node* head = NULL; 
    head = (struct Node*) malloc(1 * sizeof(struct Node)); 

и

    struct Node* head = (struct Node*) malloc(1 * sizeof(struct Node));
    head = NULL; 

Ответы [ 8 ]

0 голосов
/ 30 сентября 2018
struct Node* head = NULL; 
head = (struct Node*) malloc(1 * sizeof(struct Node)); 

Эта форма излишне инициализирует head в NULL (нулевой указатель), а затем немедленно переназначает head на результат вызова malloc().Это не является прямым вредом - и большинство компиляторов могут избежать этой начальной инициализации.Однако, это не очень хорошая привычка.

Явное преобразование типов (также называемое приведением) не требуется в C. Это также приведет к неопределенному поведению, если нет предшествующего #include <stdlib.h>, поскольку - без <stdlib.h> - компилятор примет, что malloc() вернет int.

Если предшествующего #include <stdlib.h> нет, то без преобразования типов присвоение вызовет ошибку компиляции, поскольку intне может быть неявно преобразован в указатель.Преобразование типов позволяет скомпилировать код путем принудительного преобразования из int в указатель.В результате malloc() возвращает указатель void, это значение преобразуется в int, а int преобразуется обратно в указатель.Проблема в том, что указатели не гарантированно выживают в этом цикле конверсий, поэтому head может не указывать на что-либо действительное.Поэтому любое последующее использование этого указателя (например, для доступа к членам узла) даст неопределенное поведение.

Таким образом, две вещи улучшат этот пример:

  • непосредственно инициализируют head,вместо того, чтобы инициализировать его как NULL, а затем сразу переназначить.
  • исключить преобразование типа

Конечный результат будет

struct Node* head = malloc(1 * sizeof(struct Node)); 

или, чтобы избежатьпроблемы, связанные с набором struct Node дважды (например, замена одного, но не другого в будущем)

struct Node* head = malloc(1 * sizeof(*head)); 

Правильность вышеприведенного в вашей программе в целом зависит от того, какой другой кодработает с head.

Во втором случае

struct Node* head = (struct Node*) malloc(1 * sizeof(struct Node));
head = NULL; 

первая строка имеет тот же чистый эффект, что и первый пример, и предложенные мной корректировкик этому образцу применимы.

Второе утверждение (head = NULL) немедленно отбрасывает значение, возвращаемое malloc().Ваша программа теперь не имеет возможности использовать эту память, а также не может освободить ее (поскольку значение, сохраненное malloc(), не сохраняется ни в одной переменной).Однако эта память, выделенная malloc(), останется выделенной, даже если ваша программа больше не сможет получить к ней доступ.В стандартном C нет средств, которые бы вызывали магическое освобождение во время работы вашей программы.

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

0 голосов
/ 29 сентября 2018

Во второй версии вы перезаписываете значение указателя, полученное из malloc(), с помощью NULL и, следовательно, больше не можете получить доступ к этой памяти, ситуация называется утечка памяти : определенно не то, что вам нужно.

Первая версия не является неправильной, но инициализация указателя на NULL является избыточной, поскольку вы немедленно сохраняете возвращаемое значение malloc() в ней.Компилятор, вероятно, пропустит это избыточное хранилище, но для удобства чтения вы можете упростить его следующим образом:

struct Node *head = (struct Node*)malloc(1 * sizeof(struct Node));

Обратите внимание, что приведение возвращаемого значения malloc бесполезно в C и несколько подвержено ошибкам.Рекомендуется просто написать:

struct Node *head = malloc(1 * sizeof(struct Node));

На самом деле, 1 * обычно опускается:

struct Node *head = malloc(sizeof(struct Node));

Обратите внимание, что использование явных ошибок также подвержено ошибкам.введите аргумент malloc, так как компилятор C не будет выполнять никакой проверки согласованности между выделенным размером и типом указателя.Если позже вы измените тип head или по ошибке используете другой тип, размер может быть неправильным.Для более безопасного способа указания размера для выделения используйте целевой тип указателя назначения:

struct Node *head = malloc(sizeof(*head));

Для еще более безопасной альтернативы используйте calloc(), который инициализирует память для всех битов 0, чтона большинстве современных аппаратных средств это нулевое значение для целочисленных членов и членов с плавающей запятой и нулевое значение указателя для всех типов указателей.Еще одним преимуществом является то, что вы можете указать количество элементов для выделения, как вам кажется:

struct Node *head = calloc(1, sizeof(*head));  // all members initialized to zero

Во всех случаях настоятельно рекомендуется проверить, успешно ли выделено память:

struct Node *head = calloc(1, sizeof(*head));  // all members initialized to zero
if (head == NULL) {
    fprintf(stderr, "memory allocation failed\n");
    exit(EXIT_FAILURE);
}

Вы можете еще больше упростить выражение с помощью sizeof *head вместо sizeof(*head).Это эквивалентно, скобки являются избыточными.Я лично опускаю их только для голого идентификатора, как в sizeof head, но использую их для любого другого выражения.И наоборот, скобки требуются для типа, как в sizeof(struct Node)

0 голосов
/ 29 сентября 2018

"Если так, то какой путь правильный?"подразумевает, что по крайней мере 1 из 2 является хорошим выбором.

Используйте эти аксиомы

  1. Предпочитают сухой против мокрой код.Бросьте бросок.Нет необходимости переназначать переменную, которая не использовалась.

  2. Получение ресурсов является инициализацией .По возможности инициализируйте переменную.

  3. Проверьте успешность выделения.Успешен ли вызов *alloc()?

  4. Выделить размер ссылочного объекта, а не его тип.Проще кодировать правильно, просматривать и поддерживать.


Давайте рассмотрим третий вариант, так как 2 фрагмента кода OP не совсем соответствуют всем этим аксиомам.

struct Node* head = malloc(sizeof *head); 

// Check for allocation failure
if (head == NULL) {
  // Handle out of memory in some fashion
  fprintf(stderr, "Out of memory\n");
  exit(EXIT_FAILURE);
} 
0 голосов
/ 29 сентября 2018

Вы можете даже написать struct Node* head = (struct Node*) malloc(sizeof(struct Node)) без инициализации NULL, поскольку вы объявляете, а затем непосредственно выделяете память.Используйте bzero(head, sizeof(struct Node)); (включая strings.h), если вы действительно хотите заполнить нулями то, что вы ранее выделяли.

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

0 голосов
/ 29 сентября 2018

Первый является «правильным», но делает ненужное присвоение head = NULL, так как сразу за ним следует второе присвоение head = ... malloc (...), перезаписывая присвоение NULL.Обратите внимание, что malloc () вернет NULL, если ему не удастся выделить память.

0 голосов
/ 29 сентября 2018

Все операторы в 'c' выполняются последовательно (кроме переходов, таких как циклы for или while).

Итак, в первом фрагменте вы объявили переменную 'head' и присвоили ей значение NULL, во-вторых, вы выделили память и присвоили ее адрес head, переопределяя предыдущее значение NULL,Теперь вы можете использовать переменную 'head', чтобы указать на выделенную память в программе.

Во втором фрагменте вы сделали противоположность.Сначала вы выделили память и присвоили ее адрес head, затем перезаписали это значение значением NULL.Теперь вы не можете указывать на выделенную память, так как вы потеряли указатель.Ваша программа не будет работать, и ваша память просочилась, так как она не может быть освобождена позже (потому что у нее нет информации об указателе).

0 голосов
/ 29 сентября 2018

Версия B является более сжатой версией Версии A, если вы удалите вторую строку (с = NULL), потому что это отменяет всю работу, которую вы сделали в первой строке.

Давайте разберем ее,и начните с (struct Node*) malloc(1 * sizeof(struct Node)).Вызов malloc выделяет определенный объем памяти, который в 1 раз больше размера struct Node.Начальная часть этого просто говорит "рассматривать это как указатель на struct Node";без этого он просто рассматривается как «указатель на ... что-то» (он же void *).

На самом деле не нужно , что (struct Node *) в начале,поскольку переменная, которой вы ее назначаете, является struct Node * переменной, но некоторые люди предпочитают ее.Я лично не пишу это, потому что он останавливает компьютер, предупреждая меня об ошибке определенного типа, вызванной опечаткой, которую я часто делаю.

Теперь о различиях между версиями.Версия A:

  1. Устанавливает указатель на NULL.
  2. Выделяет некоторую память.
  3. Устанавливает указатель для указания на эту память.

Версия B:

  1. Выделяет часть памяти.
  2. Устанавливает указатель, указывающий на эту память.
  3. Устанавливает указатель на NULL ...

Вы можете понять, почему это может быть проблемой.

  • У вас есть указатель на NULL вместо того, где вы можете хранить данные;и
  • Вы запросили память для своей программы, но не можете free ее использовать;в вашей программе произошла утечка памяти.

Это плохо.Я рекомендую использовать версию B, но только первую строку из нее;никогда не устанавливайте malloc 'd указатель на NULL без free, в противном случае ваша программа будет медленно ломаться.

0 голосов
/ 29 сентября 2018

Первый правильный.Второй приводит к утечкам памяти и (возможно; если вы продолжаете использовать head) к ошибкам сегмента.

В случае второго вы назначаете указатель на фрагмент кода, возвращаемый из mallocдо head.И сразу после этого вы перезаписываете это значение с помощью NULL, в результате чего переменная head становится практически бесполезной.В то же время вы теряете указатель на память malloc d.Вы никогда не можете использовать или восстановить эту память, которая называется «утечка памяти».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...