Код не работает, когда я ссылаюсь на двойной указатель

Почему я не могу правильно хранить двойной указатель и ссылаться на него?

Этот код работает:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct _node{
    int nodeNumber;
    int weight;
}* Node;

int main(int argc, char **argv){
    Node nodeList = calloc(3, sizeof(struct _node));

    // Used for testing
    nodeList->nodeNumber = 9;
    printf("Node Number: %d\n", nodeList->nodeNumber);

    return 0;
}

но когда я пытаюсь сделать структуру двойным указателем и ссылаться на нее следующим образом:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct _node{
        int nodeNumber;
        int weight;
}** Node;



int main(int argc, char **argv){
        Node nodeList = calloc(3, sizeof(struct _node));

        // Used for testing
        nodeList[0]->nodeNumber = 9;
        printf("Node Number: %d\n", nodeList[0]->nodeNumber);

        return 0;
}

Моя программа работает в течение секунды, а затем падает. Ни ошибки, ничего. Я думал, что ссылка на структуру с

nodeList[0]->nodeNumber = 9;

будет работать, но, видимо, это не так.

Также я хотел бы отметить, что я знаю, что создание указателя или двойного указателя непосредственно в структуре обычно считается плохой практикой, но это часть задания, и определение структуры было дано и должно использоваться «как есть». Конечной целью является создание массива или связанных списков. Часть связанных списков будет найдена, как я думаю, я понимаю, но это проблема.

------------------------------- РЕДАКТИРОВАТЬ ------------------ ------------------

Я изменил свой код на это:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct _node{
        int nodeNumber;
        int weight;
}** Node;



int main(int argc, char **argv){
        Node nodeList = malloc(sizeof(struct _node *));

        // Used for testing
        nodeList[0]->nodeNumber = 9;
        printf("Node Number: %d\n", nodeList[0]->nodeNumber);

        return 0;
}

но моя программа все еще падает.


person I_am_learning_now    schedule 22.01.2019    source источник
comment
Измените ** на * и все заработает. (Вы объявили массив указателей на структуру.)   -  person Paul Ogilvie    schedule 22.01.2019
comment
..и не указатели typedef! Это смущает всех.   -  person Paul Ogilvie    schedule 22.01.2019
comment
Это не работает, когда я меняю ** на * . Кроме того, как я уже говорил выше, объявление структуры должно быть двойным указателем.   -  person I_am_learning_now    schedule 22.01.2019
comment
Как я упоминал выше, это часть задания, и описание структуры было дано мне и должно использоваться как есть. В противном случае я бы вообще не использовал указатели в typedef.   -  person I_am_learning_now    schedule 22.01.2019
comment
Пожалуйста, прочтите тег wiki для двойного указателя (нажмите на тег под вопросом). Это неоднозначный термин, который может означать либо указатель на указатель, либо double*.   -  person Keith Thompson    schedule 22.01.2019


Ответы (3)


Если вы настаиваете на использовании двойных указателей, вам следует действовать следующим образом:

typedef struct _node{
    int nodeNumber;
    int weight;
} Node;

и в вашей функции:

    Node **nodelist = malloc(3 * sizeof(Node *));
    for (int i=0; i<3; i++)
        nodelist[i]= calloc(1,sizeof(Node));

Обратите внимание, что я не использую указатели в typedef, потому что это очень запутанно. Вместо этого я объявляю nodelist двойным указателем.


Итак, ваш профессор настаивает на использовании двойного указателя в typedef (я предлагаю вам попросить вашего профессора посетить stackoverflow.com....). Затем действуйте следующим образом:

typedef struct _node{
    int nodeNumber;
    int weight;
} **Node;

и в вашей функции:

    Node nodelist = malloc(3 * sizeof(*nodelist));
    for (int i=0; i<3; i++)
        nodelist[i]= calloc(1,sizeof(*nodelist[i]));

Здесь я использую не имя типа, а имя переменной для определения выделяемого размера: *nodelist разыменовывает нодлист в struct _node *, а *nodelist[i] разыменовывает его в фактический struct _node (обратите внимание, что значение i здесь не важно; используется только для указания компилятору, что предназначен элемент массива).

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

person Paul Ogilvie    schedule 22.01.2019
comment
Я ценю вашу помощь, но, как я уже сказал выше, я должен использовать код КАК ЕСТЬ. Мой профессор настаивает на использовании двойных указателей в объявлении typedef. Я сделал рабочую программу другим способом, но, к сожалению, вынужден сделать так. Я согласен, что это очень запутанно и глупо, однако я должен придерживаться спецификаций задания. - person I_am_learning_now; 22.01.2019
comment
Большое спасибо, Павел! Оно работает! Я попытался инициализировать его сам по-другому, но он не совсем работал, но, похоже, ваш способ работает! Спасибо за ваше терпение! - person I_am_learning_now; 22.01.2019

С

typedef struct _node{
    int nodeNumber;
    int weight;
}* Node;

и

Node nodeList = calloc(3, sizeof(struct _node));

вы заставляете nodeList указывать на первый элемент массива из struct _node элементов.

С

typedef struct _node{
        int nodeNumber;
        int weight;
}** Node;

тогда nodeList будет указателем на первый элемент массива указателей на struct _node, но поскольку вы используете calloc, все эти указатели в массиве будут NULL< /сильный>. Разыменование указателя NULL недопустимо и приводит к неопределенное поведение.

Кроме того, ваше распределение неверно, поскольку вы по-прежнему выделяете память для трех элементов struct _node вместо элементов struct _node *.

И как правило: никогда не прячьте указатели за псевдонимами типов. Это затрудняет чтение, понимание и поддержку кода.

person Some programmer dude    schedule 22.01.2019
comment
@I_am_learning_now nodeList[0] — это указатель, но куда он указывает? Благодаря вашему использованию calloc все указатели в массиве будут NULL. - person Some programmer dude; 22.01.2019
comment
Ах, хорошо, поэтому я, вероятно, должен вместо этого использовать malloc и изменить свое распределение на указатель - это то, что я собираю. Я попробую это и посмотрю, работает ли это - person I_am_learning_now; 22.01.2019
comment
Итак, когда я изменил свой код, у меня все еще возникает та же проблема. Проверьте мое редактирование выше для нового кода. однако программа все равно вылетает. Какие-либо предложения? - person I_am_learning_now; 22.01.2019
comment
@I_am_learning_now Использование malloc ничего не меняет, и на самом деле я бы сказал, что это усугубляет ситуацию. Теперь вы выделяете массив указателей, но каждый указатель является неопределенным. Указатели в массиве не будут автоматически инициализироваться и указывать на узлы. Вы должны сделать это сами. Как 2_. - person Some programmer dude; 22.01.2019
comment
Ах к я думаю, что это моя проблема! Извините, я не уверен в C. Я попробую их инициализировать и посмотреть, что произойдет. - person I_am_learning_now; 22.01.2019

Другие ответили на вопрос, но на случай, если кто-то столкнется с этим и заинтересуется, как это сделать правильно:

  • Никогда не прячьте указатели за определениями типов.
  • Никогда не используйте type**, когда вам действительно нужен 2D-массив. Это следует использовать только для таких вещей, как таблица строк переменной длины, которая не является двумерным массивом. Дополнительные сведения см. в разделе Правильное выделение многомерных массивов.
  • Всегда free() то, что вы malloc(). Конечно, в большинстве случаев ОС сделает это за вас. Но, вызывая free(), мы можем выявлять и обнаруживать ошибки в другом месте кода, такие как утечки памяти, оборванные указатели и т. д., которые все проявляются в виде сбоя программы при вызове free().

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

#include <stdio.h>
#include <stdlib.h>

typedef struct {
  int nodeNumber;
  int weight;
} Node;

int main (void)
{
  Node* nodeList = calloc(3, sizeof *nodeList);

  nodeList[0].nodeNumber = 9;
  printf("Node Number: %d\n\n", nodeList->nodeNumber);

  free(nodeList);


  /****************************************************************************/

  const size_t x = 2;
  const size_t y = 3;
  Node (*nodeList2D)[y] = calloc(x, sizeof *nodeList2D);

  int count = 0;
  for(int i=0; i<x; i++)
  {
    for(int j=0; j<y; j++)
    {
      nodeList2D[i][j].nodeNumber = count++;
      printf("Node (%d, %d): %d\n", i, j, nodeList2D[i][j].nodeNumber);
    }
  }

  free(nodeList2D);

  return 0;
}

Обратите внимание, что прием sizeof *nodeList2D при вызове malloc/calloc адаптируется к используемому типу. В случае 2D-массива это даст нам размер одного 1D-массива (такой же, как 3 * sizeof(Node)), а затем мы выделяем 2 таких фрагмента памяти с помощью calloc.

person Lundin    schedule 22.01.2019