Here I want to cover two great uses for the CONTAINING_RECORD macro. CONTAINING_RECORD has been a part of the DDK since forever, and it has a second cousin in Standard C by the name of offsetof.
For those that don’t use it regularly, it looks about like this:
typedef struct _BUFFER {
LIST_ENTRY e;
UCHAR *buf;
} BUFFER, *PBUFFER;
...
PLIST_ENTRY entry = RemoveHeadList(&listHead);
PBUFFER a = CONTAINING_RECORD(entry, BUFFER, e);
The macro returns a pointer to a data structure given the address of an element inside that structure, as shown above. Although you can get by without it most of the time, there are a couple of good reasons to use it.
First, it helps prevent bugs by keeping you from hard-coding structure offsets elsewhere in your code. For example, take the BUFFER struct above. You could, of course, simply cast BUFFER to LIST_ENTRY, but that encodes the position of e forever in your code. If you change the position (e.g. by adding a member above e in the definition), you will break all instances of code that rely on this layout.
Using CONTAINING_RECORD, on the other hand, allows you to write offset-independent code to refer to elements within the structure. Obviously this won’t work for code that was compiled against a different version of the structure – this is a compile-time technique. But it can mask otherwise silent errors. (Well, silent until the code path in question is executed…)
The reason that they’re silent is also the second great reason to use CONTAINING_RECORD: casts. I’ve written before about how much I like casts in code, so I’m implicitly for anything that eliminates casts. Since CONTAINING_RECORD returns the right type by definition, you no longer have to cast it. So:
PBUFFER buf = (PBUFFER)RemoveHeadList(&listHead);
becomes:
PBUFFER buf = CONTAINING_RECORD(
RemoveHeadList(&listHead), BUFFER, e);
This has the added bonus of being able to tell when things change out from under it, so it won’t produce as many silent error of the type described above.
Hello Steve,
yes, CONTAINING_RECORD returns the right type by definition. Anyway, I tend to disagree that this is better than a cast, since it contains a cast, too, as expected:
(From inc/ddk/wxp/ntdef.h:)
#define CONTAINING_RECORD(address, type, field) ((type *) [...])
Thus, the cast is (still) there, with all of its disadvantages. I can still write
ABC *buf = CONTAINING_RECORD(
RemoveHeadList(&listHead), ABC, e);
if a type of ABC exists and contains an element of name “e”, even if it does not have anything in common with your above BUFFER structure.
- Spiro.
Indeed, the cast is buried in the macro. We’re converting a type; a cast is inevitable somewhere.
But, the syntax is cleaner. If nothing else, it’s easier to grep for, and beyond that, it’s easier to understand what the programmer was trying to do.
As you say, it is still possible to make mistakes with CONTAINING_RECORD, but it certainly seems to be no worse than a naked C-style cast, and (for the above reasons) I think it’s better.