Что такое "форвардная декларация" и разница между "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;
Ответы
Ответ 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()
.