21

I was reading the chapter in Beautiful Code on the Linux kernel and the author discusses how Linux kernel implements inheritance in the C language (amongst other topics). In a nutshell, a 'base' struct is defined and in order to inherit from it the 'subclass' struct places a copy of the base at the end of the subclass struct definition. The author then spends a couple pages explaining a clever and complicated macro to figure out how many bytes to back in order to convert from the base part of the object to the subclass part of the object.

My question: Within the subclass struct, why not declare the base struct as the first thing in the struct, instead of the last thing?

The main advantage of putting the base struct stuff first is when casting from the base to the subclass you wouldn't need to move the pointer at all - essentially, doing the cast just means telling the compiler to let your code use the 'extra' fields that the subclass struct has placed after the stuff that the base defines.

Just to clarify my question a little bit let me throw some code out:

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?
}

If one happens to have a pointer to the "dev" field inside of a usb_device object then in order to cast it back to that usb_device object one needs to subtract 8 from that pointer. But if "dev" was the first thing in a usb_device casting the pointer wouldn't need to move the pointer at all.

Any help on this would be greatly appreciated. Even advice on where to find an answer would be appreciated - I'm not really sure how to Google for the architectural reason behind a decision like this. The closest I could find here on StackOverflow is: why to use these weird nesting structure

And, just to be clear - I understand that a lot of bright people have worked on the Linux kernel for a long time so clearly there's a good reason for doing it this way, I just can't figure out what it is.

Community
  • 1
  • 1
MikeTheTall
  • 3,214
  • 4
  • 31
  • 40
  • 3
    This looks more like composition than inheritance to me. Is there really code in the Linux kernel that downcasts `struct device`? – nwellnhof Jan 20 '15 at 11:55
  • In Linux kernel there is no problem to put it before, after, or in the middle. This specific excerpt is a sample of style. The real user anyway calls container_of macro to get a subclass from a basic class. Thus, `struct usb_device *ud = container_of(p, struct device, dev);`, where p is a pointer to `struct device` object. – 0andriy Jan 20 '15 at 12:55
  • Guess: Well, a cast on a value (not a pointer) would perform something akin to slicing maybe which would let you use it as a `device`. This would not be the case if it was placed first. – Benjamin Gruenbaum Jan 20 '15 at 14:12
  • nwellnhof - Yes, what I showed above is composition. There was code a little later in the chapter that invoked a function based on something like dev->someField->function() so I assume that they're using function pointers and more-or-less recreating the full (data+methods) inheritance that C++ does, just in C. – MikeTheTall Jan 20 '15 at 19:15
  • Andy Shevchenko - I agree that their code clearly works, and can work no matter where you put it in the kernel. The question is whether one can eliminate the need to have code executing at runtime to subtract 8 from the address in order to cast a usb_device back up to a basic device. – MikeTheTall Jan 20 '15 at 19:17
  • You meant to usb_device, ain't you? Otherwise it is an additional. Anyway, doing such microoptimization might make sense. – 0andriy Jan 22 '15 at 08:11
  • Since it's the kernel I think it has to do with cache line optimization. With `usb_a` as first it fits on a single cache line which makes it faster to access than if `struct device` were to occupy the first (few?) cache lines which would unnecessarily trash the cache and make access to `usb_a` that little bit slower. – Tommi Komulainen Feb 18 '15 at 12:55

4 Answers4

12

The Amiga OS uses this "common header" trick in a lot of places and it looked like a good idea at the time: Subclassing by simply casting the pointer type. But there are drawbacks.

Pro:

  • You can extend existing data structures
  • You can use the same pointer in all places where the base type is expected, no pointer arithmetic needed, saving precious cycles
  • It feels natural

Con:

  • Different compilers tend to align data structures differently. If the base structure ended with char a;, then you could have 0, 1 or 3 pad bytes afterwards before the next field of the subclass starts. This led to quite nasty bugs, especially when you had to maintain backwards compatibility (i.e. for some reason, you have to have a certain padding because an ancient compiler version had a bug and now, there is lots of code which expects the buggy padding).
  • You don't notice quickly when you pass the wrong structure around. With the code in your question, fields get trashed very quickly if the pointer arithmetic is wrong. That is a good thing since it raises chances that a bug is discovered more early.
  • It leads to an attitude "my compiler will fix it for me" (which it sometimes won't) and all the casts lead to a "I know better than the compiler" attitude. The latter one would make you automatically insert casts before understanding the error message, which would lead to all kinds of odd problems.

The Linux kernel is putting the common structure elsewhere; it can be but doesn't have to be at the end.

Pro:

  • Bugs will show early
  • You will have to do some pointer arithmetic for every structure, so you're used to it
  • You don't need casts

Con:

  • Not obvious
  • Code is more complex
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
9

I'm new to the Linux kernel code, so take my ramblings here with a grain of salt. As far as I can tell, there is no requirement as to where to put the "subclass" struct. That is exactly what the macros provide: You can cast to the "subclass" structure, regardless of its layout. This provides robustness to your code (the layout of a structure can be changed, without having to change your code. Perhaps there is a convention of placing the "base class" struct at the end, but I'm not aware of it. I've seen lots of code in drivers, where different "base class" structs are used to cast back to the same "subclass" structure (from different fields in the "subclass" of course).

S.C. Madsen
  • 5,100
  • 5
  • 32
  • 50
5

I don't have fresh experience from the Linux kernel, but from other kernels. I'd say that this doesn't matter at all.

You are not supposed to cast from one to the other. Allowing casts like that should only be done in very specific situations. In most cases it reduces the robustness and flexibility of the code and is considered quite sloppy. So the deepest "architectural reason" you're looking for might just be "because that's the order someone happened to write it in". Or alternatively, that's what the benchmarks showed would be the best for performance of some important code path in that code. Or alternatively, the person who wrote it thinks it looks pretty (I always build upside-down pyramids in my variable declarations and structs if I have no other constraints). Or someone happened to write it this way 20 years ago and since then everyone else has been copying it.

There might be some deeper design behind this, but I doubt it. There's just no reason to design those things at all. If you want to find out from an authoritative source why it's done this way, just submit a patch to linux that changes it and see who yells at you.

Art
  • 19,807
  • 1
  • 34
  • 60
  • This is hilarious: "submit a patch [...] and see who yells at you" :) I think it's kinda brilliant. While I'm not going to try this myself I appreciate your suggesting it simply because never in a million years would I have thought about it. +1 just for that :) – MikeTheTall Jan 20 '15 at 19:27
1

It's for multiple inheritance. struct dev isn't the only interface you can apply to a struct in the linux kernel, and if you have more than one, just casting the sub class to a base class wouldn't work. For example:

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;
};
Jake
  • 2,106
  • 1
  • 24
  • 23