【问题标题】:Object methods in C: memory leaksC 中的对象方法:内存泄漏
【发布时间】:2017-06-20 13:07:16
【问题描述】:

我对如何在 C 中正确实现对象有疑问。

我从方法返回对象是否比从不返回对象并像这样在参数列表中通过引用来执行它更容易发生内存泄漏?

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

这样malloc()的调用只在构造函数中进行,比较容易控制。

我是 C 中这种封装的新手,所以我不确定这是否能解决我的问题。我只是希望我的代码具有可扩展性,并且我发现如果我继续这样做,内存泄漏将无处不在,而且很难调试。通常是如何处理的?我的代码是否在正确的轨道上?

我的问题是,如果我有这个:

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_destroy

头文件:

#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

实施文件:

#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;
}

【问题讨论】:

  • 有什么理由要在 c 中模拟对象吗? c++不是更容易吗?
  • 我根本不会使用 malloc/free,主要是因为 malloc/free 是相当昂贵的操作,性能较低。
  • 只是一个旁注。我怀疑您正在实施四元数以在可能对性能敏感的事情上进行数学运算。不断的 malloc 绝对会破坏你的表现。如何避免内存泄漏和同时不破坏性能的一个不错的答案是:不要 malloc。不惜一切代价避免使用 malloc,让用户处理分配和释放。
  • 我要告诉你一件事,你的指针间距让你很难调试,就像this-&gt;x*p-&gt;y 这样的乘法一样。您已将空间放在不属于它们的位置,并将它们从它们所在的位置删除。您希望在视觉上关联事物以提高清晰度,而不是将事物塞在一起。一般除指针外,运算符两边都有空格(一个空格)。
  • 为了让它看起来正确,我做了太多的编辑,但我现在已经完成了答案。

标签: c pointers object struct memory-leaks


【解决方案1】:

实际上,如果某些函数具有更新某些内容并返回新构造值的两者副作用,则情况会变得更糟。说吧,谁记得scanf返回值?

看看GNU Multi Precision Arithmetic Library,我建议以下是一种合理的解决方案(实际上,他们更进一步让内存分配让用户头痛,而不是图书馆):

  1. 只有构造函数才能创建新对象。我还建议所有构造函数的名称都遵循相同的模式。
  2. 只有析构函数才能销毁已经存在的对象。
  3. 函数接受输入参数(例如,+ 的两侧)和输出参数(将结果放在哪里,覆盖之前对象中的所有内容),如下所示:

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

这样您将始终清楚地看到对象何时被创建/销毁,并且可以确保没有泄漏或双重释放。当然,这会阻止您将多个操作“链接”在一起,并使您手动管理临时变量以获得中间结果。但是,我相信您仍然必须在 C 中手动执行此操作,因为即使是编译器也不太了解动态分配变量的生命周期。

实际上,如果我们不离开。 3 并添加“库不管理内存”子句,我们将得到更容易出错的解决方案(从库的角度来看),这将要求库的用户随心所欲地管理内存。这样你也不会用malloc/free内存分配锁定用户,这是一件好事。

【讨论】:

    【解决方案2】:

    无需动态分配四元数。四元数具有固定的大小,因此您可以只使用普通的四元数。如果您进行整数计算,您也可以只使用ints,而无需为每个int 动态分配空间。

    不使用malloc/free的想法(未经测试的代码)

    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;
    }
    

    用法

    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)); 
    

    没有mallocs 也没有frees 所以不存在内存泄漏的可能,性能会更好。

    【讨论】:

    • 哇,这是我没想到的……我知道有一种优雅的方式。我现在看不到这可能会引起可扩展性的任何潜在问题,你们中有人看到任何问题吗?
    • 析构函数?...也许只有当四元数结构的成员之一是指向某个数组或结构的指针时,它才会有代码?
    • @Angel 你不需要使用这种方法的任何析构函数。
    • @MichaelWalz,那么我们在构造函数中创建一个结构,然后将其复制到外部并在之后删除内部的副本,与 malloc( )?如果结构真的很大怎么办?
    • @Angel 你是对的,如果结构真的很大,那么复制可能是一个性能问题。但请注意,删除里面的副本并不需要任何时间。分配和释放局部变量的成本为零(至少在我知道的所有实现中)。
    【解决方案3】:

    在我看来你说错了。

    您还需要检查 malloc() 是否返回 NULL 并处理该故障(例如通过显示内存不足错误消息并在失败时退出,因为这通常是不可恢复的)。

    更新:

    由于您需要释放中间结果,因此您必须采取更多措施来保留指针。

    更新 2

    @MichaelWalz 的方法很棒。如果不需要,为什么要处理所有分配和指针管理?但是,如果您确实使用指针并分配内存,则必须保留指针并确保释放内容并小心地传递/重用它们。我更新了我的示例来处理嵌套调用。

    更新 3

    我不得不从函数调用参数中删除 &'s,因为你传入的指针已经是你想要指向的地址。您没有正确分配函数的输出。请注意,固定示例中的指针分配也有所不同。


    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);
    

    【讨论】:

    • 呃...我尝试了一些,但我认为根据您的使用示例,该示例可以满足您的需求。
    猜你喜欢
    • 2014-05-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-31
    • 1970-01-01
    相关资源
    最近更新 更多