简体   繁体   中英

Object methods in C: memory leaks

I have questions about how to properly implement objects in C.

Is the fact that I return objects from methods more prone to memory leaks than, for example, never returning an object and doing it by reference in the argument list like this?

extern void quaternion_get_product(Quaternion * this, Quaternion * q, Quaternion * result);

This way the malloc() call is only done in the constructor, so it is easier to control.

I am new to this kind of encapsulation in C, so I am not sure if this would be the way to solve my problem. I just want my code to be scalable, and I see that if I continue doing this, memory leaks are going to be all around, and it is going to be really hard to debug. How is it usually approached? Am I on the right track with my code?

My question is, if I have this:

Quaternion p = *quaternion_create(1, 0, 0, 0);
Quaternion q = *quaternion_create(1, 0, 1, 0);
Quaternion r = *quaternion_create(1, 1, 1, 0);
Quaternion s = *quaternion_create(1, 1, 1, 1);

p = *quaterion_get_product(&p, &q); // Memory leak, old p memory block is not being pointed by anyone

Quaternion t = *quaternion_get_product(&q, quaternion_get_product(&s, &r)); 

Memory leaks are present when nesting function calls, intermediate memory blocks are not pointed by any existing pointer, cannot call quaternion_destroy

Header file:

#ifndef __QUATERNIONS_H_
#define __QUATERNIONS_H_

#include <stdlib.h>

typedef struct Quaternion Quaternion;

struct Quaternion {
    float w;
    float x;
    float y;
    float z;
};

extern Quaternion *quaternion_create(float nw, float nx, float ny, float nz);
extern void quaternion_destroy(Quaternion *q);
extern Quaternion *quaternion_get_product(Quaternion *this, Quaternion *q);
extern Quaternion *quaternion_get_conjugate(Quaternion *this);
extern float quaternion_get_magnitude(Quaternion *this);
extern void quaternion_normalize(Quaternion *this);
extern Quaternion *quaternion_get_normalized(Quaternion *this);
#endif

Implementation file:

#include "quaternion.h"
#include <math.h>

Quaternion *quaternion_create(float nw, float nx, float ny, float nz) {
    Quaternion *q = malloc(sizeof(Quaternion));

    q->w = nw;
    q->x = nx;
    q->y = ny;
    q->z = nz;
    return q;
}

void quaternion_destroy(Quaternion *q) {
    free(q);
}

Quaternion *quaternion_get_product(Quaternion *this, Quaternion *p) {
        Quaternion *return_q = quaternion_create(
            this->w * p->w - this->x * p->x - this->y * p->y - this->z * p->z,  // new w
            this->w * p->x + this->x * p->w + this->y * p->z - this->z * p->y,  // new x
            this->w * p->y - this->x * p->z + this->y * p->w + this->z * p->x,  // new y
            this->w * p->z + this->x * p->y - this->y * p->x + this->z * p->w
        );
        return return_q;
}

Quaternion *quaternion_get_conjugate(Quaternion *this)
{
        return quaternion_create(this->w, -this->x, -this->y, -this->z);
}

float quaternion_get_magnitude(Quaternion *this) {
        return sqrt(this->w * this->w + this->x * this->x + this->y * this->y + this->z * this->z);
}

void quaternion_normalize(Quaternion *this) {
        float m = quaternion_get_magnitude(this);
        this->w /= m;
        this->x /= m;
        this->y /= m;
        this->z /= m;
}

Quaternion *quaternion_get_normalized(Quaternion *this) {
        Quaternion *r = quaternion_create(this->w, this->x, this->y, this->z);
        quaternion_normalize(r);
        return r;
}

Actually, it can get even worse if some function has both side effect of updating something and return a newly constructed value. Say, who remembers that scanf returns a value?

Looking at GNU Multi Precision Arithmetic Library I'd suggest that the following is one reasonable solution (actually, they go even further by making memory allocation user's headache, not library's):

  1. Only a constructor can create a new object. I'd also suggest that all constructors' name follow the same pattern.
  2. Only a destructor can destroy an already existing object.
  3. Functions take both input arguments (say, two sides of + ) and output argument(s) (where to put the result, overriding everything that was in the object before), like this:

mpz_add (a, a, b); /* a=a+b */

That way you will always clearly see when objects are created/destroyed and can ensure that there are no leaks or double frees. Of course, that prevents you from "chaining" multiple operations together and makes you manually manage temporary variables for intermediate results. However, I believe you'd still have to do that manually in C, because even compiler does not know much about dynamically allocated variable's lifetime.

Actually, if we leave no. 3 and add the "library does not manage memory" clause, we will get even more error-prone solution (from library's point of view), which will require library's user to manage memory however they want. That way you also don't lock user with malloc / free memory allocation, which is kind of good thing.

There is no need to dynamically allocate your quaterions. A quaternion has a fixed size and therefore you can use just plain Quaternions. If you make integer calculations you also use just int s without dynamically allocating space for each int .

Idea for not using malloc/free (untested code)

Quaternion quaternion_create(float nw, float nx, float ny, float nz) {
    Quaternion q;

    q.w = nw;
    q.x = nx;
    q.y = ny;
    q.z = nz;
    return q;
}

Quaternion quaternion_get_product(Quaternion *this, Quaternion *p) {
    Quaternion return_q = quaternion_create(
        this->w * p->w - this->x * p->x - this->y * p->y - this->z * p->z,  // new w
        this->w * p->x + this->x * p->w + this->y * p->z - this->z * p->y,  // new x
        this->w * p->y - this->x * p->z + this->y * p->w + this->z * p->x,  // new y
        this->w * p->z + this->x * p->y - this->y * p->x + this->z * p->w
    );
    return return_q;
}

Usage

Quaternion p = quaternion_create(1, 0, 0, 0);
Quaternion q = quaternion_create(1, 0, 1, 0);
Quaternion r = quaternion_create(1, 1, 1, 0);
Quaternion s = quaternion_create(1, 1, 1, 1);

p = quaterion_get_product(&p, &q);

Quaternion t = quaternion_get_product(&q, quaternion_get_product(&s, &r)); 

There are no malloc s nor free s so there is no possible memory leak and the performance will be better.

Looks to me like you're calling it wrong.

You also need to check to see if malloc() returns NULL and handle that failure (such as by displaying an out of memory error message and exiting if it fails because that's non-recoverable generally).

UPDATE:

Since YOU need to free your intermediate result you have to take more action to retain the pointer.

UPDATE 2

@MichaelWalz approach is great. Why deal with all the allocations and pointer management if you don't need to? However, if you do use pointers and allocate memory you have to retain pointers and make sure you free things and pass them around/reuse them carefully. I've updated my example to handle the nested call.

UPDATE 3

I had to remove the &'s from the function call arguments because you're passing pointers in, which are already the address of what you want to point to. You didn't assign the output of the functions properly. Notice how the pointer assignments are different in the fixed example too.


Quaternion p = *quaternion_create(1, 0, 0, 0);
Quaternion q = *quaternion_create(1, 0, 1, 0);
Quaternion r = *quaternion_create(1, 1, 1, 0);
Quaternion s = *quaternion_create(1, 1, 1, 1);

p = *quaterion_get_product(&p, &q); // Memory leak, old p memory block is not being pointed by anyone

Quaternion t = *quaternion_get_product(&q, quaternion_get_product(&s, &r)); 

Quaternion *p = quaternion_create(1, 0, 0, 0);
Quaternion *q = quaternion_create(1, 0, 1, 0);
Quaternion *r = quaternion_create(1, 1, 1, 0);
Quaternion *s = quaternion_create(1, 1, 1, 1);


Quaternian *tmp = quaterion_get_product(p, q); 
quaternian_destroy(p);
p = tmp;
tmp = quaternion_get_product(s, r)
Quaternion *t = quaternion_get_product(q, tmp); 
quaternian_destroy(tmp);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM