При чтении исходного кода Linux (версия: 5.17.4) у меня возникает вопрос: почему заголовочный файл Linux может объявлять структуру, а затем использовать ее? Возьмем, к примеру, ipv4.h. Кажется, сначала он объявляет ctl_table_header/fib_table/sock без включения соответствующих файлов заголовков, но использует их позже в следующем определении структуры. В языке C определение структуры данных должно быть включено, прежде чем ее можно будет использовать. Здесь это правило не соблюдается. Почему он работает нормально? Заранее спасибо.
Указатели на неполные типы структур/объединений действительны.
См. en.wikipedia.org/wiki/Opaque_pointer
Это называется опережающим объявлением и может использоваться с указателем.
Основная причина, по которой это делается в больших базах кода, таких как ядро Linux, заключается в том, чтобы минимизировать количество вложенных директив #include
и сэкономить время компиляции.
Механизм, который позволяет этому работать, заключается в том, что типы структур и объединений могут быть объявлены как неполные типы, а стандарт C требует, чтобы все указатели на типы структур имели одинаковые требования к представлению и выравниванию, как друг друга, и требует, чтобы все указатели на объединение типы должны иметь одинаковые требования к представлению и выравниванию, как друг друга. Таким образом, требования к представлению и выравниванию типа указателя известны до завершения базового типа (хотя требования к размеру и выравниванию базовой структуры или типа объединения неизвестны до тех пор, пока они не будут завершены).
В языке C определение структуры данных должно быть включено, прежде чем ее можно будет использовать.
Да. Но указатель на структуру можно использовать и без определения структуры.
Стандарт C допускает объявление неполной структуры или объединения (6.7.2.1 Спецификаторы структуры и объединения).
Неполное объявление структуры/объединения состоит просто из имени структуры/объединения без определения какого-либо члена.
Неполная структура/объединение является неполным типом (6.2.5 Типы):
В различных точках единицы перевода тип объекта может быть неполным (недостаточно информации для определения размера объектов этого типа) или полным (имеющим достаточно информации).
Это означает, что:
Неполный тип можно использовать только в том случае, если размер объекта этого типа не требуется.
Т.е. указатель на неполный тип совершенно допустим (размер указателя всегда определен).
В следующем фрагменте показано использование неполного определения структуры:
struct incomplete_struct; // an incomplete structure declaration
struct incomplete_struct *foo; // a pointer to an incomplete structure definition
struct incomplete2 *bar; // here we declare an incomplete structure and define a pointer to it
В стандарте также сказано (6.2.5 Типы):
Тип может быть неполным или полным на протяжении всей единицы перевода или может менять состояния в разных точках единицы перевода.
Состояние можно изменить, добавив полное объявление (определяющее размер объекта) перед его использованием. См. следующий пример
struct incomplete *foo; // declaration of incomplete structure and definition of a pointer to it
struct incomplete // complete declaration of the structure
{
int a;
float b;
};
foo->a = 0; // assign fields
foo->b = 0.0;
В этом случае неполное определение структуры называется опережающим объявлением, и очень полезно создавать определения сложных структур или других объектов, которые ссылаются на объекты, которые еще не определены (или которые нам не нужно указывать в этом конкретном случае). единицу перевода, потому что они нам не нужны или потому что эти объекты могут меняться в разных системах и т. д.).
В качестве простого примера рассмотрим объявление двух структур, содержащих указатель на другую:
struct a // complete type declaration for structure a
{
struct b *foo; // here we forward declare struct b and define a pointer to it
.... // other fields
};
struct b // complete type declaration for structure b
{
struct a *bar;
.... // other fields
};
Приведенный выше код, хотя и абсолютно правильный, не может считаться хорошей практикой, потому что, если объявления структуры 2 не очень близки, она будет очень плохо читабельна. Лично я, даже если это излишне, предпочитаю объявлять все в начале единицы перевода, чтобы было легче следить за потоком кода. То есть:
struct a; // forward declaration for structure a
struct b; // forward declaration for structure b
.....
struct a // complete type declaration for structure a
{
struct b *foo; // here we forward declare struct b and define a pointer to it
.... // other fields
};
.....
struct b // complete type declaration for structure b
{
struct a *bar;
.... // other fields
};
Спасибо за подробный и исчерпывающий ответ, он дал мне хорошее понимание этого вопроса.
Можете ли вы поделиться фрагментом кода, демонстрирующим эту проблему? Это облегчит понимание вопроса.