#include<stdio.h>
typedef struct data
{

    int num;
    struct node *link;
}node;
main()
{
    node *s=NULL,*e=NULL,*new;
    new=malloc(sizeof(node));
    s=malloc(sizeof(node));
    s->num=10;
    s->link=new;
    new->num=8;
    new->link=NULL;
 //  printf("%d\n",s->link->num);  //// error: dereferencing pointer to incomplete type
    e=s;
    while(e!=NULL)
    {
        printf("%d",e->num);
        e=e->link;
    }

}

Для указанной выше программы на C я получил правильный результат. Но если я включу закомментированную строку, она вызовет ошибку разыменования неполного типа. Кто-нибудь может объяснить причину?

c
5
Sharon 24 Фев 2016 в 19:03

4 ответа

Лучший ответ

Это путаница с названиями.

node == struct data благодаря typedef.

Но node! = struct node. Это 2 разных типа. И struct node не был определен.

Жалко, что такая запутанная конструкция разрешена стандартом C и даже не помечена компиляторами как предупреждение. Но именно так определяется C сегодня, поэтому мы должны с этим жить.

Моя рекомендация: не используйте одинаковые имена для struct и typedef. Создайте собственное соглашение, например struct thing_s и typedef struct thing_s thing_t;. Это позволит избежать недоразумений при именовании, подобных этой.

Решение теперь довольно очевидно. Заменить:

typedef struct data { int num; struct node *link; } node;

От

typedef struct data { int num; struct data *link; } node;

И проблемный printf теперь будет работать.

Но ваш вопрос: почему ваша программа вообще работала без этого printf?

Вот это интересно.

Вернемся к первоначальному определению. Как мы уже говорили, struct node не существует и, следовательно, является неполным. Чтобы лучше понять, что мы собираемся объяснить, назовем это вместо этого struct incomplete_s. Теперь node становится:

typedef struct data { int num; struct incomplete_s *link; } node;

Он по-прежнему будет работать без проблемного printf.

Причина в том, что node определено правильно. Это структура известных размеров и типов. Потому что не имеет значения, что struct incomplete_s является неполным, поскольку link определен как указатель на него. Вы также могли бы определить void * link;, и он все равно будет работать.

void*, struct incomplete_s * или whatever * имеют одинаковый размер: все они указатели. Таким образом, можно правильно создать структуру, на которой они размещены.

В вашем основном цикле ваша программа:

   while(e!=NULL)
   {
      printf("%d\n",e->num);
      e=e->link;
   }

То есть e, который является указателем node*, принимает значение e->link, которое является указателем struct incomplete_s *.

Обратите внимание, что оба указателя должны указывать на разные типы. Но они оба являются указателями, так что да, это назначение технически возможно и разрешено стандартом.

Теперь более осторожный компилятор, скорее всего, выдаст здесь предупреждение, так как вы не должны смешивать указатели разных типов. Это тихое приведение типов, которое является рецептом для будущих ошибок. Я не знаю, какой компилятор вы используете, но вы можете увеличить его уровень предупреждения (используйте «уровень предупреждения 4» для Visual или -Wall -Wextra для gcc), и ему, вероятно, не понравится эта операция = .

Более явное приведение типов решит эту проблему (*): e = (node*)(e->link);

Теперь это больше не тихо, за все отвечает программист, предупреждения исчезнут.

e->link определенно существует, поэтому компилятор может получить это значение. Но s->link->num не существует, поскольку s->link есть struct incomplete_s*, и мы не знаем, что это такое, поэтому мы не знаем, есть ли у него член num .

(*) Дополнение Overkill : на каком-то более высоком уровне оптимизации это все равно будет плохо: строгий псевдоним может помешать. Таким образом, разыменование типа указателя на другой тип указателя остается опасной операцией.

1
Community 23 Май 2017 в 12:16

Самый короткий ответ: вы определили тип, которому нужна одна структура, которая не объявляется.

Причина, по которой вы получаете ошибку в прокомментированной строке, заключается в том, что это уникальная строка, которую вы использовали в качестве члена элемента ссылки в структуре узла. В любой другой части вашего кода вы не попадаете так, как вы, например, e = new->link; компилятору не нужно знать о членах структуры в это время, потому что вы сказали, что {{X1} } имеет тип node, node - это структура, а new->link - совместимая структура, так что все в порядке.

Это также причина, когда вы меняете typedef struct data { …}; на typedef struct node { …}; и все работает, потому что, когда компилятору нужна информация об узле структуры, он находит структуру с именем node и есть член с именем link, у которого есть член по имени num. Как видите, typedef - это просто псевдоним. Надеюсь, это поможет вам понять.

0
Jean Jung 25 Фев 2016 в 01:26

struct node не объявлен в этом коде, поэтому struct node* - неполный тип.

Я полагаю, что struct node *link; в объявлении структуры должно быть struct data *link;.

0
MikeCAT 24 Фев 2016 в 16:07

Прежде всего, вы должны изменить свою структуру и устранить ошибку комментируемой части:

typedef struct data
{

   int num;
   struct data *link;

}node;

Когда вы определяете s и new в следующих строках, нет необходимости ставить их равными NULL, это рабочая основная функция:

void main(){
   node *s,*e,*new;
   e=NULL;

   new=malloc(sizeof(node));
   s=malloc(sizeof(node));
   s->num=10;
   new->num=8;
   new->link=NULL;
   s->link=new;
   printf("%d\n",s->link->num);  /// works fine without any error
   e=s;
   while(e!=NULL)
   {
      printf("%d\n",e->num);
      e=e->link;
   }
return;
}
2
ReshaD 24 Фев 2016 в 16:52