20

Я читал главу в Beautiful Code на ядре Linux, и автор обсуждает, как ядро ​​Linux реализует наследование на языке C (среди других тем). В двух словах определена «базовая» структура и для того, чтобы наследовать от нее, структура «подкласса» помещает копию базы в конце определения структуры подкласса. Затем автор проводит пару страниц, объясняя умный и сложный макрос, чтобы выяснить, сколько байтов назад, чтобы преобразовать из базовой части объекта в часть подкласса объекта.Ядро Linux: почему структуры «подкласса» помещают информацию базового класса в конец?

Мой вопрос: В подклассе структуры, то почему бы не объявить базовую-структуру как первой вещи в структурах, вместо последней вещи?

Главное преимущество кладки базового элемента структуры в первую очередь заключается в том, что при отливке от базы к подклассу вам вообще не нужно будет перемещать указатель - по существу, делая приведение просто означает, что компилятор должен использовать ваш код «дополнительные» поля, которые структура подкласса помещала после материала, определяемого базой.

Просто чтобы прояснить мой вопрос немного дайте мне пролить некоторый код из:

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 является: why to use these weird nesting structure

И, просто чтобы быть ясно, - я понимаю, что много ярких людей работали на ядре Linux в течение длительного времени, так ясно, что есть веская причина сделать это таким образом, я просто не могу понять, что это такое.

+3

Это больше похоже на композицию, чем наследование для меня. Действительно ли код в ядре Linux опускает «struct device»? – nwellnhof

+0

В ядре Linux нет никаких проблем, чтобы поставить его до, после или в середине. Эта конкретная выдержка является образцом стиля. Настоящий пользователь в любом случае вызывает container_of macro, чтобы получить подкласс из базового класса. Таким образом, 'struct usb_device * ud = container_of (p, struct device, dev);', где p является указателем на объект struct device. – 0andriy

+0

Угадайте: ну, отличное от значения (не указатель) могло бы выполнить что-то похожее на нарезку, возможно, это позволит вам использовать его как «устройство». Это было бы не так, если бы оно было помещено первым. –

ответ

12

Amiga OS использует этот «общий заголовок» во многих местах, и в то время это выглядело как хорошая идея: подклассификация путем простого ввода типа указателя. Но есть недостатки.

Pro:

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

Con:

  • Различные компиляторы имеют тенденцию выравнивать структуры данных по-разному. Если базовая структура закончилась с char a;, тогда у вас может быть 0, 1 или 3 байта байт после того, как начнется следующее поле подкласса. Это привело к довольно неприятным ошибкам, особенно когда вам приходилось поддерживать обратную совместимость (то есть по какой-то причине у вас должно быть определенное дополнение, потому что у древней версии компилятора была ошибка, и теперь есть много кода, который ожидает отладку багги) ,
  • Вы не заметите быстро, когда проходите неправильную структуру вокруг. С кодом в вашем вопросе, поля получаются сбой очень быстро, если арифметика указателя неверна. Это хорошо, так как это повышает вероятность того, что ошибка обнаружена более рано.
  • Это приводит к тому, что «мой компилятор исправит это для меня» (что иногда не будет), и все приведения приводят к отношению «Я знаю лучше, чем компилятор». В последнем случае вы автоматически вставляете броски, прежде чем понимать сообщение об ошибке, что приведет к возникновению всех нечетных проблем.

Ядро Linux ставит общую структуру в другом месте; это может быть, но не обязательно должно быть в конце.

Pro:

  • Bugs покажет рано
  • Вам придется сделать некоторые арифметические операции над указателями для каждой структуры, поэтому вы привыкли к нему
  • Вам не нужно проливает

Con:

  • Не очевидно
  • код является более сложным
+0

Это объясняет технику «общего заголовка», но вопрос о «общем хвосте». – MSalters

9

Я новичок в коде ядра Linux, поэтому возьмите мои промахи здесь с солью. Насколько я могу судить, нет требования относительно того, где разместить структуру «подкласса». Это именно то, что предоставляют макросы: вы можете использовать структуру «подкласса» независимо от ее компоновки. Это обеспечивает надежность вашего кода (макет структуры можно изменить, не изменяя свой код. Возможно, существует соглашение о размещении структуры «базового класса» в конце, но я не знаю об этом. Я видел много кода в драйверах, где различные структуры «базового класса» используются для возврата к той же структуре «подкласса» (конечно, из разных полей в «подклассе»).

5

Я не имеют новый опыт работы с ядром Linux, но из других ядер. Я бы сказал, что это вообще не имеет значения.

Вы не должны отбрасывать их из одного в другое. в очень специфических ситуациях. В большинстве случаев это снижает надежность и гибкость кода и считается довольно неряшливый. Таким образом, самая глубокая «архитектурная причина», которую вы ищете, может быть просто «потому что это тот порядок, в котором кто-то написал его». Или, альтернативно, это то, что показали показанные тесты, было бы лучшим для производительности некоторого важного кода в этом коде. Или, наоборот, человек, который его написал, думает, что он выглядит красиво (я всегда создаю перевернутые пирамиды в своих объявлениях и структурах переменных, если у меня нет других ограничений).Или кто-то написал это так 20 лет назад, и с тех пор все остальные копировали его.

За этим может быть какой-то более глубокий дизайн, но я сомневаюсь. Просто нет причин для разработки этих вещей вообще. Если вы хотите узнать из авторитетного источника, почему это делается так, просто отправьте патч в linux, который изменит его и посмотрит, кто на вас кричит.

+0

Это весело: «отправьте патч [...] и посмотрите, кто кричит на вас» :) Я думаю, что это красиво. Хотя я не собираюсь это делать сам, я ценю, что вы предлагаете это просто потому, что никогда через миллион лет я бы не подумал об этом. +1 только для этого :) – MikeTheTall

1

Это для множественного наследования. 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; 
};