Подтвердить что ты не робот

Что такое "форвардная декларация" и разница между "typedef struct X" и "struct X"?

Я новичок в программировании на C и знаю разницу между объявлением типа struct и объявлением структуры typedef. Я наткнулся на ответ, в котором говорится, что если мы определим struct наподобие:

typedef struct { 
    some members;
} struct_name;

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

Кроме того, я хотел знать, что для следующего кода:

typedef struct NAME { 
    some members;
} struct_alias;

Есть ли разница между NAME и struct_alias? Или оба равны, поскольку struct_alias является псевдонимом struct NAME?

Кроме того, мы можем объявить переменную типа struct NAME следующим образом:

struct_alias variable1;

и/или как:

struct NAME variable2;

или как:

NAME variable3; 
4b9b3361

Ответ 1

Объявления struct forward могут быть полезны, когда вам нужны циклические объявления структуры. Пример:

struct a {
    struct b * b_pointer;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

Когда объявляется struct a он еще не знает спецификаций struct b, но вы можете перенаправить ссылку на нее.

Когда вы печатаете def анонимной структуры, компилятор не позволяет вам использовать ее имя перед typedef.

Это незаконно:

struct a {
    b * b_pointer;
    int c;
};

typedef struct {
    struct a * a_pointer;
    void * d;
} b;

// struct b was never declared or defined

Хотя это законно:

struct a {
    struct b * b_pointer;
    int c;
};

typedef struct b {
    struct a * a_pointer;
    void * d;
} b;

// struct b is defined and has an alias type called b

Так что это:

typedef struct b b;
// the type b referes to a yet undefined type struct b

struct a {
    b * struct_b_pointer;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

И это (только в C, незаконно в C++):

typedef int b;

struct a {
    struct b * struct_b_pointer;
    b b_integer_type;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

// struct b and b are two different types all together. Note: this is not allowed in C++

Ответ 2

Передовая декларация - это обещание определить что-то, что вы делаете компилятору, в тот момент, когда определение не может быть выполнено. Компилятор может использовать ваше слово для интерпретации других объявлений, которые он не сможет интерпретировать иначе.

Общим примером является struct, созданный как node в связанном списке: вам нужно поместить указатель на node в struct, но компилятор не позволит вам это сделать без либо прямое объявление, либо тег:

// Forward declaration
struct element;
typedef struct {
    int value;
    // Use of the forward declaration
    struct element *next;
} element; // Complete definition

и поэтому он не может использоваться для форвардной декларации

Я думаю, что авторский момент заключался в том, что предоставление тэга struct эквивалентно объявлению вперед:

typedef struct element {
    int value;
    // No need for a forward declaration here
    struct element *next;
} element;

Ответ 3

Forward декларация - это объявление, предшествующее фактическому определению, обычно для того, чтобы иметь возможность ссылаться на объявленный тип, когда определение недоступно. Конечно, не все может быть сделано с объявленной не определенной структурой, но в определенном контексте ее можно использовать. Такой тип называется неполным, и существует ряд ограничений на его использование. Например:

struct X; // forward declaration

void f(struct X*) { }  // usage of the declared, undefined structure

// void f(struct X) { }         // ILLEGAL
// struct X x;                  // ILLEGAL
// int n =sizeof(struct X);     // ILLEGAL

// later, or somewhere else altogether
struct X { /* ... */ };

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

В вашем примере struct NAME и struct_alias действительно эквивалентны.

struct_alias variable1;
struct NAME variable2;

являются правильными;

NAME variable3;

нет, так как в C требуется ключевое слово struct.

Ответ 4

struct_alias и struct NAME одинаковы, struct_alias является псевдонимом struct NAME

Оба они одинаковы и разрешены

struct_alias variable1;  

struct NAME variable1; 

это незаконно

NAME variable3;   

См. статью в Форвардная декларация

Ответ 5

Как указывалось ранее, форвардная декларация в C/С++ представляет собой объявление чего-либо с фактическим отсутствием определения. Его объявление говорит компилятору "есть тип данных ABC".

Давайте сделаем вид, что это заголовок для некоторого хранилища ключей/значений my_dict.h:

...
struct my_dict_t;
struct my_dict_t* create();

char* get_value(const struct my_dict_t* dict, const char* name);
char* insert(struct my_dict_t* dict, const char* name, char* value);
void destroy(struct my_dict_t* dict);
...

Вы ничего не знаете о my_dict_t, но на самом деле, для использования магазина вам не нужно знать:

#include "my_dict.h"
...
struct my_dict_t* dict = create();
if(0 != insert(dict, "AnEntry", strdup("AValue"))) {
    ...
}
...

Причиной этого является: вы используете POINTERS только для структуры данных.

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

Это будет иметь значение только в том случае, если вы попытаетесь получить к ним доступ, например

struct my_dict_t* dict = create();
printf("%s\n", dict->value);  /* Impossible if only a forward decl is available */

Итак, для реализации функций вам требуется фактическое определение my_struct_t. Вы можете сделать это в исходном файле my_dict.c следующим образом:

#include "my_dict.h"

struct my_dict_t {
    char* value;
    const char* name;
    struct my_dict_t* next;
}

struct my_dict_t* create() {
    return calloc(1, sizeof(struct my_dict_t));
}

Это удобно для нескольких ситуаций, таких как

  • Для решения зависимостей круговых типов, как объяснил Сергей Л.,
  • Для инкапсуляции, как в приведенном выше примере.

Итак, остается вопрос: почему мы не можем просто отказаться от прямого объявления при использовании вышеперечисленных функций? В конце концов, компилятору было бы достаточно знать, что все dict являются указателями.

Однако компилятор выполняет проверки типов: Он должен убедиться, что вы не делаете что-то вроде

...
int i = 12;
char* value = get_value(&i, "MyName");
...

Не нужно знать, как выглядит my_dict_t, но ему нужно знать, что &i не является типом указателя get_value().