3 ms·
‘ You can’t extend structs or do anything really OO-like, but it’s a useful pattern to think with’ That’s not quite true. If you define 2 structs so that they
by spacedcowboy 4y ago
‘ You can’t extend structs or do anything really OO-like, but it’s a useful pattern to think with’
That’s not quite true. If you define 2 structs so that they start the same (eg: both with “int x; int y” in your example), pointers can be passed to functions with either struct type. You can use this to add fields (eg: int z) to structures, and extend a 2d vector into a 3d one…
With a bit of creative thought, and constructive use of pointer-to-functions, you can do quite a bit of OOP stuff in C.
- dahfizz 4y agoYou can also put function pointers in a struct, which is sometimes useful. The syntax is ugly, but you can do a lot of OO stuff that way too.
- mannykannot 4y agoIn the days when grokking OO was taken to validate your membership of the cognoscenti, this led to some pointlessly awful code.
- fwip 4y agoIn 2nd year of college, one of my friends and I figured this out on our own, which was a really rewarding experience. The code did look pretty hairy, though. Details for anyone interested: The CS course project was to write a game solver for a variety of games with perfect information (e.g: tic-tac-toe), and they highly suggested we use object-oriented design and recursion + backtracking. They also let us pick any language we wanted, and being computer engineers, between the two of us, we were most comfortable with C. So we kind of started writing our project and implementing the first game, and when we got to the second, we were scratching our heads like, "Is it possible to just... take a pointer to a function?" "Yeah, it's just somewhere in memory, right?" And then everything fell into place, and we just had to define a struct with pointers to game state and "methods", and our TA was baffled that we did the project in C but we got a great grade.
- shultays 4y agoI would be surprised if C guarantees those x and y to have same offsets Plus you cant cast and dereference one type's pointer to the other. That would be UB
- spacedcowboy 4y agoModulo any padding rules, I would be surprised if C didn't store them in the same offsets, the compatible-type specification[1] and equality operator[2] (also see footnote 109) would make implementation much harder if you tried to do it any other way. The way to work around that of course, and to make sure that no matter what, the parent/child objects are compatible to the parent-only level is to do something like: // this structure defines a node of a linked list and // it only holds the pointers to the next and the previous // nodes in the linked list. struct list_head { struct list_head *next; // pointer to the node next to the current one struct list_head *prev; // pointer to the node previous to the current one }; // list_int holds a list_head and an integer data member struct list_int { struct list_head list; // common next and prev pointers int value; // specific member as per implementation }; // list_str holds a list_head and a char * data member struct list_str { struct list_head list; // common next and prev pointers char \* str; // specific member as per implementation }; Often the 'parent' structure would have an 'int objType', which can be switch'd on, to make sure that the receiving function knows what to do. I'm not really seeing any undefined behaviour here. I'm pretty sure this technique is decades old, btw. I know at one point the linux kernel used it, not sure if it still does.. If you're happy with being C11-compliant, then remove any names for the 'parent' structure in the child structures, making them "anonymous structs" at which point they are [3] considered to be part of the same struct as the parent. [1]: http://port70.net/~nsz/c/c11/n1570.html#6.2.7p1 http://port70.net/~nsz/c/c11/n1570.html#6.2.7p1 [2]: http://port70.net/~nsz/c/c11/n1570.html#6.5.9p6 http://port70.net/~nsz/c/c11/n1570.html#6.5.9p6 [3]: http://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p13 http://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p13
- hddqsb 4y agoEven if the fields are stored in the same offsets, there are modern optimisations that rely on "strict aliasing" and that would cause issues (unless the workaround you described is used).
- hddqsb 4y agoThis technique is used in Berkley sockets: the `sockaddr` type (https://pubs.opengroup.org/onlinepubs/009695399/basedefs/sys/socket.h.html https://pubs.opengroup.org/onlinepubs/009695399/basedefs/sys...) is the "base" type for other types such as `sockaddr_ip` (for IPv4). But it's undefined behaviour according to ISO C due to the "strict aliasing" rule: https://en.cppreference.com/w/c/language/object#Strict_aliasing https://en.cppreference.com/w/c/language/object#Strict_alias...
- LegionMammal978 4y agoStrict aliasing allows you to convert between a pointer to a struct and a pointer to its first member, since two objects exist at that address, one with the type of the struct and one with the type of the first member. > A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
- hddqsb 4y agoYes, that's the workaround described by @spacedcowboy and @leni53. I should have clarified that my comment on undefined behaviour was specifically for the case of two structs that start the same (e.g. "int x; int y;") as opposed to composition.
- icedchai 4y agoThe Amiga used this technique extensively throughout its OS.
- leni536 4y agoNo, this breaks aliasing. The defined way to do something like this is to have the smaller struct as the first member of the larger one. The first member is guaranteed to have the same address as the outer object. The common initial sequence trick is guaranteed to work with unions in limited circumstances.