And why couldn't I use what I just wrote (post #75)? I mean, why won't that also work?
And why couldn't I use what I just wrote (post #75)? I mean, why won't that also work?
Am I missing some posts here? Anyway, post #75 looks ok to me. An alternative version, but it's not any "better", just an alternative version:
Code:void freeSystem(TaxiSystem* system){ TaxiSystem *tmpdrv; TaxiSystem *tmpord; while (system->driversQueue){ tmpdrv = system->driversQueue; system->driversQueue = system->driversQueue->next; free(tmpdrv); } while (system->ordersQueue){ tmpord = system->ordersQueue; system->ordersQueue = system->ordersQueue->next; free(tmpord); } free(system); }
And what vart posted (#74) is obviously also an alternative version, similarly correct, right?
Yes, I was trying to update my post, but couldn't get back to the forum. Here is the alternate version I wanted to show, similar to vart's example with only one temp pointer. The only difference is temp is used for the current instance to be freed. I don't know if there's any advantage to either version (vart's or this one), they both do the same thing and I've seen it done both ways.
Code:void freeSystem(TaxiSystem* system){ void * temp; while (system->driversQueue){ temp = system->driversQueue; system->driversQueue = system->driversQueue->next; free(temp); } while (system->ordersQueue){ temp = system->ordersQueue; system->ordersQueue = system->ordersQueue->next; free(temp); } free(system); }
Last edited by rcgldr; 06-17-2013 at 02:41 PM.
I preferred writing it this way:
Code:void freeSystem(TaxiSystem* system){ void* tmp = NULL; while (system->driversQueue){ tmp = system->driversQueue->next; free(system->driversQueue); system->driversQueue = tmp ; } while (system->ordersQueue){ tmp = system->ordersQueue->next; free(system->ordersQueue); system->ordersQueue = tmp ; } free(system); }
I would recommend abstracting out the freeing of driversQueue and ordersQueue into separate functions, like so:
This is a better design, and has several benefits:Code:void freeDriver(Driver *drivers) { Driver *tmp; while (drivers) { tmp = drivers->next; free(drivers); drivers = tmp; } } void freeOrder(Order *orders) { // similar to freeDriver } void freeSystem(TaxiSystem* system) { if (system) { // ensure system is non-null freeDriver(system->driversQueue); freeOrder(system->ordersQueue); free(system); } }
1. If you need a Driver or Order queue that is not part of a TaxiSystem, you can still free it without re-writing the whlie loop in a separate function.
2. The freeSystem function is easier to read, write and maintain.
3. Separation of concerns. The TaxiSystem should not care how a Driver or Order queue is implemented. This way you can change the underlying implementation of a Driver or Order queue (say a priority queue, FIFO queue, etc) without worrying about changing the TaxiSystem implementation.
NOTE: This sort of abstraction should be used throughout your program. That goes for your add, lookup, remove, etc functions.
If you put the pointers to next first in structures, you can use common code to work with linked lists similar to C++ class inheritance. For example:
Code:typedef struct node{ struct node* next; }Node; typedef struct driver{ struct driver* next; char name[101]; int priority; }Driver; typedef struct order{ struct order* next; int orderID; int isSpecific; char specificDriver[101]; }Order; void freeNodes(Node * nodes) { Node *tmp; while (nodes) { tmp = nodes->next; free(nodes); nodes = tmp; } } void freeSystem(TaxiSystem* system) { if (system) { // ensure system is non-null freeNodes((Node *)(system->driversQueue)); freeNodes((Node *)(system->ordersQueue)); free(system); } }
@rcgldr:
That's not a very safe way to do it:
Pointers can have different sizes and alignments in some systems (uncommon on most desktop environments but still possible). A safer alternative:Originally Posted by C11 6.3.2.3p7
This is allowed since the first member is guaranteed to have the same address as the struct itself:Code:typedef struct node Node; struct node { Node *next; }; typedef struct driver Driver; struct driver { Node node; char name[101]; int priority; }; typedef struct order Order; struct order { Node node; int orderID; int isSpecific; char specificDriver[101]; }; void freeNodes(Node *nodes) { Node *tmp; while (nodes) { tmp = nodes->next; free(nodes); nodes = tmp; } } void freeSystem(TaxiSystem* system) { if (system) { // ensure system is non-null freeNodes((Node *) system->driversQueue); freeNodes((Node *) system->ordersQueue); free(system); } }
There's a tiny bit more work to build and traverse the list, but at least it's guaranteed to work.Originally Posted by C11 6.7.2.1p13
I still don't like that system however. I prefer to use a generic list container:
It costs a few more calls to malloc and free, but it makes a great library and is highly reusable across all my programs. But better still, there exist tons of free libraries out there that already do this, and do it well.Code:typedef struct list List; struct list { List *next; void *data; };
I had the impression that all pointers to structures would be of the same size on any system, or more generally, that all conventional pointers would be of the same size on the same system, and as mentioned in C11 6.7.2.1p13 , that there would never be any unnamed padding at the beginning of any structure.
I'm also thinking that if pointer size is variable, then how would malloc() and free() work for a resized pointer?
However a one time check could be added to make sure that the code in my previous post would not be an issue, by comparing sizeof(...->next) and offsetof(..., next) for all structures used in a program to make sure they are equal.
I've also use the generic list structure, with a few additional members as part of a messaging interface between threads on embedded systems using a preemptive multi-threading kernel.
Last edited by rcgldr; 06-17-2013 at 07:44 PM.
O_oThere's a tiny bit more work to build and traverse the list, but at least it's guaranteed to work.
Nope.
You have moved the cast from the cleanup routines to the generation routines, and if the sizes or alignment aren't compatible, the assertion leading you to forward an alternative, the generation side of the code loses or misalignes information leading to failure.Code:driver * s1 = malloc(sizeof(*s1) * 1); tmp1->next = s1; // We aren't assigning to a `driver *'. // We aren't assigning to a `order *'. static order s2; // We are assigning to a `node *'. tmp2->next = &s2; // If `node *' is smaller we lose information. // If `node *' is larger it could conceivably // require different alignment which may break // where `driver *' or `order *' are created // independentaly of this `node *'.
Your code relies on being able to "roundtrip" a pointer to a structure of one type through a pointer to a structure of a different type yielding the `void *' address returned from `malloc' exactly as much as the code rcgldr posted.
The first member having the same address as the structure proper, which chains giving you the address of a `Node *', is a "red herring"; (The code posted by rcgldr works the same in that the first member is reliably accessed.) the reliable access to the first member says nothing about the size of that member or how a compiler may use low or high bits of a `void *' to store addresses of different types. (For example, an environment using 4 bytes for function pointers, 4 bytes for near data pointers, and 8 bytes for far data pointers with an 8 byte `void *' may use the lowest 4 bytes for function pointers and the highest 4 bytes for near data pointers allowing a debugger optimization.) So, the allocation, offset, and alignment made part of the `node *' in the `node' structure tells only part of the story when we will be allocating, assigning, and otherwise using `driver *' and `order *' through that `node *'.
The standard says you only get this "roundtrip" thing through `void *'; you aren't guaranteed to get it through another pointer.
If the pointers aren't compatible, they just aren't compatible.
I think this is a silly thing to worry about. The standard allowing different sized pointers and alignment has more to do with near/far data pointers, function pointers, and in C++ member pointers than allowing for different sized pointers to user defined types.
Soma
Last edited by phantomotap; 06-17-2013 at 08:13 PM. Reason: *derp*