Почему заголовочный файл Linux может объявить структуру, а затем использовать ее?

При чтении исходного кода Linux (версия: 5.17.4) у меня возникает вопрос: почему заголовочный файл Linux может объявлять структуру, а затем использовать ее? Возьмем, к примеру, ipv4.h. Кажется, сначала он объявляет ctl_table_header/fib_table/sock без включения соответствующих файлов заголовков, но использует их позже в следующем определении структуры. В языке C определение структуры данных должно быть включено, прежде чем ее можно будет использовать. Здесь это правило не соблюдается. Почему он работает нормально? Заранее спасибо.

Можете ли вы поделиться фрагментом кода, демонстрирующим эту проблему? Это облегчит понимание вопроса.

Mureinik 19.08.2024 11:22

Указатели на неполные типы структур/объединений действительны.

Ian Abbott 19.08.2024 11:22

См. en.wikipedia.org/wiki/Opaque_pointer

Andrew Henle 19.08.2024 11:34

Это называется опережающим объявлением и может использоваться с указателем.

wohlstad 19.08.2024 11:37

Основная причина, по которой это делается в больших базах кода, таких как ядро ​​Linux, заключается в том, чтобы минимизировать количество вложенных директив #include и сэкономить время компиляции.

Ian Abbott 19.08.2024 11:39

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

Ian Abbott 19.08.2024 11:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В языке 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
};

Спасибо за подробный и исчерпывающий ответ, он дал мне хорошее понимание этого вопроса.

nn huang 19.08.2024 13:34

Другие вопросы по теме