User: Password:
|
|
Subscribe / Log in / New account

Object-oriented design patterns in the kernel, part 2

Object-oriented design patterns in the kernel, part 2

Posted Jun 7, 2011 21:57 UTC (Tue) by nix (subscriber, #2304)
Parent article: Object-oriented design patterns in the kernel, part 2

There is another variation on structure embedding I have used in the past, which is to have the subtypes' data appended directly to the structure containing the supertype, with appropriate macros / functions to map from one offset to the other and to do appropriate casting. This gives you the advantages of structure-embedding subtyping without costing a dereference or an extra allocation, but has disadvantages, notably that you have to ensure correct alignmewnt of the trailing subtype field (easy enough) and that sizeof() no longer works. So this is only really usable when the datatype is itself hidden and only used within one translation unit, such that it is the only thing that can take the sizeof this structure, and can add an appropriate size for the subtype in use whenever a sizeof is needed.

Combine this with an ADT pointer and you have something pretty much identical in representation to C++ classes. The code is... a good bit hairier than C++ classes though.


(Log in to post comments)

Object-oriented design patterns in the kernel, part 2

Posted Jun 7, 2011 22:29 UTC (Tue) by cmccabe (guest, #60281) [Link]

I don't think any macros are really needed, just something like this:
struct base {
int a;
int b;
char end[0] __attribute__((aligned(4)));
};

struct derived1 {
int a;
};

struct derived2 {
char b;
};
Then you have stuff like this:
void foo(struct base *b) {
    if (type == DERIVED1) {
        struct derived1 *d = (struct derived1*)b->end;
        ...
    }
}
It isn't as nice as C++, but it's ok.

Flexible array members were standardized in C99. Every compiler can do it, but I don't know if the syntax for forcing alignment is standardized yet.

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 4:52 UTC (Wed) by jzbiciak (subscriber, #5246) [Link]

I don't know if the syntax for forcing alignment is standardized yet.

What if you just made it an array of "double" or "long double"? Or, an array of a union of all of the "large" types, so you're guaranteed to get the most restrictively aligned type on the platform?

typedef union
{
    long long   ll;
    double      d;
    long double ld;
    void       *vp;
    void      (*fp)(void);  /* function pointer */
} align_u;

Then replace char end[0] __attribute__((aligned(4))); with align_u end[0];. Wouldn't that work?

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 13:16 UTC (Wed) by nix (subscriber, #2304) [Link]

That does indeed work and is exactly what I did. A hallowed ancient trick: I don't know *how* long ago I first saw it. Decades back.

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 13:14 UTC (Wed) by nix (subscriber, #2304) [Link]

Ah yes. You have access to flexible array members and can assume that you're using GCC. Alas, when I perpetrated this trick I could assume neither of these, so needed a suitable union as a final member to ensure correct alignment.

The macros are just to make things less horrendously unreadable. :)

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 4:55 UTC (Wed) by jzbiciak (subscriber, #5246) [Link]

FWIW, this sort of hack is something Dennis Ritchie once referred to as "unwarranted chumminess with the implementation." I always found that phrase amusing. :-)

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 9:48 UTC (Wed) by tialaramex (subscriber, #21167) [Link]

The outcome is also notable.

Everybody did this, the implementations all allowed it, but the standard said you couldn't rely upon it.

So they fixed the standard, introducing in C99 a way to explicitly ask for what everyone wanted here, and adding text insisting that implementations do what the developers expect in that case.

Not every case can or should be treated this way, but it's a useful option. When the conflict is between reality and the standard, consider altering the standard.

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 10:07 UTC (Wed) by jzbiciak (subscriber, #5246) [Link]

Oh, I agree. C is quite a pragmatic language, drawing its pragmatism from many directions. The fact that its compiler directive for turning on/off various vendor-defined features is named #pragma I think says a lot.

C99 also cleared up the meaning of const as it qualified pointer arguments to functions, offering up the much better suited restrict qualifier in its place. That was a case where the cookie crumbled a bit differently, but still fit with the general pragmatism of the C working group and C language. I can't really complain.

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 11:06 UTC (Wed) by etienne (guest, #25256) [Link]

C99 could have added a warning when a structure is created (and not a structure pointer allocated), so that this is catched:
$ cat tmp.c
struct str { unsigned len; char data[]; };
void fct1(struct str *ptr);
void fct2(struct str *initial) {
struct str copy = *initial;
fct1(&copy);
}
$ gcc -Wall -std=c99 -O2 -c tmp.c
$

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 11:14 UTC (Wed) by cesarb (subscriber, #6266) [Link]

I think it is the compiler which adds warnings, not the standard. So, you could report this as a bug to the gcc developers and they could add a warning, even if the standard thinks this is valid.

That is, if the warning does not exist already. -Wall does not enable all warnings. Did you try -Wextra and check the manual for the warnings neither option enables?

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 13:03 UTC (Wed) by tialaramex (subscriber, #21167) [Link]

The standard requires some diagnostics, GCC complies with the standard in this regard when in 'pedantic' mode (of course you must also tell GCC which standard you intended to comply with to get anything useful)

But yes, a useful compiler will definitely want to offer additional diagnostic information beyond that called out in the standard, and it may find that some diagnostics called for by the standard are just unhelpful, conflict with real world practice or obstruct a cool but non-standard feature they wish to offer, and so disable them at least by default.

The case outlined above doesn't strike me as worth being called out in the standard, but I can see that at least some similar cases could be usefully mentioned by the compiler, like unadorned occurrences of the assignment operator in a boolean context. You _might_ mean what you say, but you probably don't, and if you really do, you could say it more clearly for the benefit of some future maintenance programmer.

Object-oriented design patterns in the kernel, part 2

Posted Jun 9, 2011 15:34 UTC (Thu) by jwakely (subscriber, #60262) [Link]

The standard does *not* require any warnings. The diagnostics it requires are for violations of syntax rules or constraints, not for warning about questionable constructs.

The standard says something works or it doesn't, it never says "this is ok but the implementation should warn about it", so cesarb is quite right, C99 could not have added a warning, and requests for such a warning should go to compiler implementations not the standard.

Object-oriented design patterns in the kernel, part 2

Posted Jun 10, 2011 3:40 UTC (Fri) by jzbiciak (subscriber, #5246) [Link]

That's largely true. I did find at least one place (and there are likely others) where the standard suggests a warning. It doesn't mandate it though, in ยง6.4.4.2:

Recommended practice

The implementation should produce a diagnostic message if a hexadecimal constant cannot be represented exactly in its evaluation format; the implementation should then proceed with the translation of the program.

If "produc[ing] a diagnostic" and "proceed[ing] with the translation of the program" doesn't constitute a warning, I don't know what does.

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 15:15 UTC (Wed) by etienne (guest, #25256) [Link]

For this GCC version:
$ gcc --version
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3

I get no warnings:
$ gcc -Wall -Wextra -pedantic -Wvla -std=c99 -O2 -c tmp.c
$

But I never use this construct and I already have my own list of bugs for GCC in bugzillia.

Object-oriented design patterns in the kernel, part 2

Posted Jun 8, 2011 13:21 UTC (Wed) by nix (subscriber, #2304) [Link]

Oh no. What Ritchie described *was* unwarranted chumminess with the implementation, because you're declaring a short array then allocating more space and walking off the end of it. That *is* forbidden by the Standard. But allocating enough space for one structure appended to the end of another one, then doing pointer addition and a typecast? That's allowed: you're not accessing off the end of one object at all, you're accessing the object you cast to.


Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds