问题
I'm working on a C library, and part of it deals with some mathematical types and manipulating them. Each type has a factory constructor/destructor function that allocates and frees them dynamically. For example:
/* Example type, but illustrates situation very well. */
typdef struct {
float x;
float y;
float z;
} Vector3D;
/* Constructor */
Vector* Vector3D_new(float x, float y, float z) {
Vector3D* vector = (Vector3D*) malloc(sizeof(Vector3D));
/* Initialization code here...*/
return vector;
}
/* Destructor */
void Vector3D_destroy(Vector3D* vector) {
free(vector);
}
Nice & simple, and also alleviates the loads of proper initialization for a user.
Now my main concern is how to handle functions that operate upon these types (specifically how to return the result values.) Almost every binary operation will result in a new instance of the same type, and therefore, I need to consider how to give this back to the user. I could just return things by value, but passing around pointers would be preferred, since it is faster, compatible with the construct/destructor methods, and doesn't leave as much burden on the user.
Currently I have it implemented by having functions dynamically allocate the result, and then return a pointer to it:
/* Perform an operation, and dynamically return resultant vector */
Vector3D* addVectors(Vector3D* a, Vector3D* b) {
Vector3D* c = Vector3D_new(
a->x + b->x,
a->y + b->y,
a->z + b->z);
return c;
}
By returning the value directly to the user, it has the advantage of being able to be chained (e.g. to be passed directly into another function as a parameter), such as:
/* Given three Vector3D*s : a, b, & c */
float dot = dotProduct(crossProduct(a, addVectors(b, c));
But given the current method, this would result in a memory leak, since the result of addVectors() would be passed directly to crossProduct(), and the user wouldn't have a chance to free() it (and the same thing with crossProduct()'s result that is passed into dotProduct()). To make this work, a person would have to make a pointer to hold the value(s), use that, and then free() it via said pointer.
Vector3D* d = addVectors(b, c);
Vector3D* e = crossProduct(a, d);
float dot = dotProduct(e);
Vector3D_destroy(d);
Vector3d_destroy(e);
This works but is much less intuitive, and loses the chaining effect I so desire.
Another possibility is to have the operation functions take 3 arguments; two for the operands, and one to store the result in, but again not very intuitive.
My question is then: What are some elegant & productive ways of working with dynamic memory in binary operations? As a bonus, a solution that has been used in a real world library would be pretty cool. Any ideas? :)
回答1:
In addition to the memory-leak you mentioned there are a few other problems with your current system:
- Allocating to the heap is significantly slower than plain stack operations.
- Every allocation will also need to be
free()d, meaning every instance will require at least 2 function invocations, where as just using a stack based design would require none.- Since memory has to be manually managed it leaves much more room for memory leaks.
- Memory allocations can fail! A stack based system would alleviate this.
- Using pointers would require dereferencing. This is slower than direct access, and requires more (perhaps, sloppy) sytax.
In addition to this, many compilers cache the memory used for a program's stack, and can provide signifigant improvements over the heap (which is almost never cached (if possible!))
In short, simply relying on the stack for everything would be best, not only for performance, but also maintenence and clean code. The only thing to remember is that the stack is finite, and it could also be easy to go crazy. Use the stack for short term data (a binary operation result in this case), and the heap for heavier long term data.
I hope this helps! :)
Note: Much of the info in this answer is thanks to @Justin.
回答2:
Allocating inside the operator isn't as convenient as it may seem.
This is mostly because you don't have garbage collection, and also because you have to worry about failed allocations.
Consider this code:
Vector3D *v1,*v2,*v3;
Vector3d v4 = addVectors(v1,multiplyVectors(v2,v3));
Seems nice.
But what happens with the vector returned from multiplyVectors? A memory leak.
And what happens if allocation fails? A crash in some other function.
I'd go for addition in-place:void addVectors(Vector3D *target, const Vector3D *src);
This is equivalent to target += src;.
回答3:
I would do as simple as
Vector3D addVectors(Vector3D a, Vector3D b) {
Vector3D c;
c.x = a.x + b.x;
c.y = a.y + b.y;
c.z = a.z + b.z;
return c;
}
If the caller really needs it on the heap, he can copy it by himself.
来源:https://stackoverflow.com/questions/10985185/how-should-i-return-the-result-of-a-binary-operation-function-in-a-c-library