Ядро Linux: почему структуры "подкласса" помещают информацию базового класса в конец?
Я читал главу в Beautiful Code в ядре Linux, и автор обсуждает, как ядро Linux реализует наследование на языке C (среди другие темы). В двух словах определена "базовая" структура, и для того, чтобы наследовать от нее, структура "подкласса" помещает копию базы в конце определения структуры подкласса. Затем автор проводит пару страниц, объясняя умный и сложный макрос, чтобы выяснить, сколько байтов назад, чтобы преобразовать из базовой части объекта в часть подкласса объекта.
Мой вопрос: В структуре подкласса, почему бы не объявить базовую структуру как первую вещь в структуре, а не последнюю вещь?
Основным преимуществом размещения базового элемента структуры в первую очередь является приведение от базы к подклассу, в котором вам не нужно будет перемещать указатель вообще - по существу, выполнение акта просто означает указание компилятору позволить вашему коду использовать "дополнительные" поля, которые структура подкласса разместила после материала, определяемого базой.
Просто, чтобы немного разъяснить мой вопрос, позвольте мне выделить код:
struct device { // this is the 'base class' struct
int a;
int b;
//etc
}
struct usb_device { // this is the 'subclass' struct
int usb_a;
int usb_b;
struct device dev; // This is what confuses me -
// why put this here, rather than before usb_a?
}
Если у человека есть указатель на поле "dev" внутри объекта usb_device, то для того, чтобы вернуть его обратно к этому объекту usb_device, нужно вычесть 8 из этого указателя. Но если "dev" было первым делом в использовании usb_device, то указателю вообще не нужно было бы перемещать указатель.
Любая помощь по этому поводу будет очень признательна. Даже совет о том, где найти ответ, будет оценен по достоинству - я не совсем уверен, как Google за архитектурную причину такого решения. Самый близкий, который я мог найти здесь, в StackOverflow:
зачем использовать эту странную структуру вложенности
И, чтобы быть понятным - я понимаю, что многие яркие люди долгое время работали над ядром Linux, так что у него есть веская причина для этого, я просто не могу понять, что это такое.
Ответы
Ответ 1
Amiga OS использует этот трюк "общего заголовка" во многих местах, и в то время это выглядело неплохо: подклассирование путем простого ввода типа указателя. Но есть недостатки.
Pro:
- Вы можете расширить существующие структуры данных
- Вы можете использовать один и тот же указатель во всех местах, где ожидается базовый тип, не требуется арифметика указателя, экономя драгоценные циклы
- Он чувствует себя естественно
Con:
- Различные компиляторы имеют тенденцию выравнивать структуры данных по-разному. Если базовая структура закончилась с
char a;
, тогда у вас может быть 0, 1 или 3 байта байт после того, как начнется следующее поле подкласса. Это привело к довольно неприятным ошибкам, особенно когда вам приходилось поддерживать обратную совместимость (то есть по какой-то причине у вас должно быть определенное дополнение, потому что у древней версии компилятора была ошибка, и теперь есть много кода, который ожидает отладку багги).
- Вы не замечаете быстро, когда проходите неправильную структуру. С кодом в вашем вопросе, поля получаются сбой очень быстро, если арифметика указателя неверна. Это хорошо, так как это повышает вероятность того, что ошибка будет обнаружена более рано.
- Это приводит к тому, что "мой компилятор исправит это для меня" (которого это иногда не будет), и все приведения приводят к "я знаю лучше, чем компилятор". В последнем случае вы автоматически вставляете броски, прежде чем понимать сообщение об ошибке, что приведет к возникновению всех нечетных проблем.
Ядро Linux ставит общую структуру в другом месте; это может быть, но не обязательно должно быть в конце.
Pro:
- Ошибки покажутся ранним
- Вам нужно будет сделать некоторую арифметику указателя для каждой структуры, поэтому вы привыкли к ней.
- Вам не нужны роли
Con:
- Не очевидно
- Код более сложный
Ответ 2
Я новичок в коде ядра Linux, поэтому возьмите мои промахи здесь с солью. Насколько я могу судить, нет требования относительно того, где разместить структуру "подкласса" . Это именно то, что предоставляют макросы: вы можете использовать структуру "подкласса" независимо от ее компоновки. Это обеспечивает надежность вашего кода (макет структуры можно изменить, не изменяя свой код.
Возможно, существует соглашение о размещении структуры "базового класса" в конце, но я не знаю об этом. Я видел много кода в драйверах, где различные структуры "базового класса" используются для возврата к той же структуре "подкласса" (из разных полей в "подклассе", конечно).
Ответ 3
У меня нет нового опыта от ядра Linux, но из других ядер. Я бы сказал, что это не имеет значения.
Вы не должны отбрасывать от одного к другому. Разрешить подобные броски следует выполнять только в очень специфических ситуациях. В большинстве случаев это снижает надежность и гибкость кода и считается довольно неряшливым. Таким образом, самая глубокая "архитектурная причина", которую вы ищете, может быть просто "потому, что в том порядке, в котором кто-то случайно написал ее". Или, альтернативно, то, что показали показанные тесты, было бы лучшим для производительности некоторого важного кода в этом коде. Или, наоборот, человек, который его написал, думает, что он выглядит красиво (я всегда создаю перевернутые пирамиды в своих объявлениях и структурах переменных, если у меня нет других ограничений). Или кто-то написал это так 20 лет назад, и с тех пор все остальные копировали его.
За этим может быть какой-то более глубокий дизайн, но я сомневаюсь. Там просто нет причин для разработки этих вещей вообще. Если вы хотите узнать из авторитетного источника, почему это так, просто отправьте патч в linux, который изменит его и посмотрит, кто кричит на вас.
Ответ 4
Это для множественного наследования. struct dev - это не единственный интерфейс, который вы можете применить к структуре в ядре linux, и если у вас более одного, просто отбрасывание подкласса в базовый класс не будет работать. Например:
struct device {
int a;
int b;
// etc...
};
struct asdf {
int asdf_a;
};
struct usb_device {
int usb_a;
int usb_b;
struct device dev;
struct asdf asdf;
};