me , colleague trying achieve simple polymorphic class hierarchy. we're working on embedded system , restricted using c compiler. have basic design idea compiles without warnings (-wall -wextra -fstrict-aliasing -pedantic) , runs fine under gcc 4.8.1.
however, bit worried aliasing issues not understand when becomes problem.
in order demonstrate have written toy example 'interface' ihello , 2 classes implementing interface 'cat' , 'dog.
#include <stdio.h> /* -------- ihello -------- */ struct ihello_; typedef struct ihello_ { void (*sayhello)(const struct ihello_* self, const char* greeting); } ihello; /* helper function */ void sayhello(const ihello* self, const char* greeting) { self->sayhello(self, greeting); } /* -------- cat -------- */ typedef struct cat_ { ihello hello; const char* name; int age; } cat; void cat_sayhello(const ihello* self, const char* greeting) { const cat* cat = (const cat*) self; printf("%s cat! name %s , %d years old.\n", greeting, cat->name, cat->age); } cat cat_create(const char* name, const int age) { static const ihello cathello = { cat_sayhello }; cat cat; cat.hello = cathello; cat.name = name; cat.age = age; return cat; } /* -------- dog -------- */ typedef struct dog_ { ihello hello; double weight; int age; const char* sound; } dog; void dog_sayhello(const ihello* self, const char* greeting) { const dog* dog = (const dog*) self; printf("%s dog! can make sound: %s %d years old , weigh %.1f kg.\n", greeting, dog->sound, dog->age, dog->weight); } dog dog_create(const char* sound, const int age, const double weight) { static const ihello doghello = { dog_sayhello }; dog dog; dog.hello = doghello; dog.sound = sound; dog.age = age; dog.weight = weight; return dog; } /* client code */ int main(void) { const cat cat = cat_create("mittens", 5); const dog dog = dog_create("woof!", 4, 10.3); sayhello((ihello*) &cat, "good day!"); sayhello((ihello*) &dog, "hi there!"); return 0; } output:
good day! cat! name mittens , 5 years old.
hi there! dog! can make sound: woof! 4 years old , weigh 10.3 kg.
we're pretty sure the 'upcast' cat , dog ihello safe since ihello first member of both these structs.
our real concern 'downcast' ihello cat , dog respectively in corresponding interface implementations of sayhello. cause strict aliasing issues? our code guaranteed work c standard or lucky works gcc?
update
the solution decide use must standard c , cannot rely on e.g. gcc extensions. code must able compile , run on different processors using various (proprietary) compilers.
the intention 'pattern' client code shall receive pointers ihello , able call functions in interface. however, these calls must behave differently depending on implementation of ihello received. in short, want identical behaviour oop concept of interfaces , classes implementing interface.
we aware of fact code works if ihello interface struct placed first member of structs implement interface. limitation willing accept.
according to: does accessing first field of struct via c cast violate strict aliasing?
§6.7.2.1/13:
within structure object, non-bit-field members , units in bit-fields reside have addresses increase in order in declared. pointer structure object, suitably converted, points initial member (or if member bit-field, unit in resides), , vice versa. there may unnamed padding within structure object, not @ beginning.
the aliasing rule reads follows (§6.5/7):
an object shall have stored value accessed lvalue expression has 1 of following types:
- a type compatible effective type of object,
- a qualified version of type compatible effective type of object,
- a type signed or unsigned type corresponding effective type of object,
- a type signed or unsigned type corresponding qualified version of effective type of object,
- an aggregate or union type includes 1 of aforementioned types among members (including, recursively, member of subaggregate or contained union), or
- a character type.
according fifth bullet above , fact structures contain no padding @ top sure 'upcasting' derived struct implements interface pointer interface safe, i.e.
cat cat; const ihello* catptr = (const ihello*) &cat; /* upcast */ /* inside client code */ void greet(const ihello* interface, const char* greeting) { /* users not need know whether interface points cat or dog. */ interface->sayhello(interface, greeting); /* dereferencing should safe */ } the big question whether 'downcast' used in implementation of interface function(s) safe. seen above:
void cat_sayhello(const ihello* hello, const char* greeting) { /* following statement safe if know * fact hello points cat? * violate strict aliasing rules? */ const cat* cat = (const cat*) hello; /* access internal state in cat */ } also note changing signature of implementation functions to
cat_sayhello(const cat* cat, const char* greeting); dog_sayhello(const dog* dog, const char* greeting); and commenting out 'downcast' compiles , runs fine. however, generates compiler warning function signature mismatch.
i've been doing objects in c many years doing kind of composition doing here. i'm going recommend not simple cast describing, justify need example. instance timer callback mechanism used layered implementation:
typedef struct msectimer_struct msectimer; struct msectimer_struct { doublelinkedlistnode m_list; void (*m_expiry)(msectimer *); unsigned int m_ticks; unsigned int m_needsclear: 1; unsigned int m_user: 7; }; when 1 of these timers expires managing system calls m_expiry function , passes in pointer object:
timer->m_expiry(timer); then take base object amazing:
typedef struct basedoer_struct basedoer; struct basedoer_struct { debugid m_id; void (*v_beamazing)(basedoer *); //object's "virtual" function }; //basedoer's version of basedoer's 'virtual' beamazing function void basedoer_v_basedoer_beamazing( basedoer *self ) { printf("basically, i'm amazing\n"); } my naming system has purpose here, that's not focus. can see variety of object oriented function calls might needed:
typedef struct delaydoer_struct delaydoer; struct delaydoer_struct { basedoer m_basedoer; msectimer m_delaytimer; }; //delaydoer's version of basedoer's 'virtual' beamazing function void delaydoer_v_basedoer_beamazing( basedoer *base_self ) { //instead of casting, have compiler smarter delaydoer *self = getobjectfrommember(delaydoer,m_basedoer,base_self); msectimer_start(m_delaytimer,1000); //make them wait } //delaydoer::delaytimer's version of msectimer's 'virtual' expiry function void delaydoer_delaytimer_v_msectimer_expiry( msectimer *timer_self ) { delaydoer *self = getobjectfrommember(delaydoer,m_delaytimer,timer_self); basedoer_v_basedoer_beamazing(&self->m_basedoer); } i've been using same macro getobjectfrommember since around 1990, , somewhere along line linux kernel created same macro , called container_of (the parameters in different order though):
#define getobjectfrommember(objecttype,membername,memberpointer) \ ((objecttype *)(((char *)memberpointer) - ((char *)(&(((objecttype *)0)->membername))))) which relies on (technically) undefined behavior (dereferencing null object), portable every old (and new) c compiler i've ever tested. newer version requires offsetof macro, part of standard (as of c89 apparently):
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );}) i, of course, prefer name, whatever. using method makes code not rely on putting base object first, , makes second use case possible, find useful in practice. of aliasing compiler issues managed within macro (casting through char * think, i'm not standards lawyer).
Comments
Post a Comment