如何解决由C ++包装器中C对象之间的交互引起的与内存相关的错误?

问题描述 投票:0回答:3

问题

我正在围绕面向对象的C库编写一个薄的C ++包装器。这个想法是要自动化内存管理,但是到目前为止,它并不是很自动。基本上,当我使用包装器类时,会遇到各种内存访问和不适当的释放问题。

C库的最小示例

假设C库由AB类组成,每个类都有一些与之关联的“方法”:

#include <memory>
#include "cstring"
#include "iostream"

extern "C" {
typedef struct {
    unsigned char *string;
} A;

A *c_newA(const char *string) { 
    A *a = (A *) malloc(sizeof(A)); // yes I know, don't use malloc in C++. This is a demo to simulate the C library that uses it. 
    auto *s = (char *) malloc(strlen(string) + 1);
    strcpy(s, string);
    a->string = (unsigned char *) s;
    return a;
}

void c_freeA(A *a) {
    free(a->string);
    free(a);
}

void c_printA(A *a) {
    std::cout << a->string << std::endl;
}


typedef struct {
    A *firstA;
    A *secondA;
} B;

B *c_newB(const char *first, const char *second) {
    B *b = (B *) malloc(sizeof(B));
    b->firstA = c_newA(first);
    b->secondA = c_newA(second);
    return b;
}

void c_freeB(B *b) {
    c_freeA(b->firstA);
    c_freeA(b->secondA);
    free(b);
}

void c_printB(B *b) {
    std::cout << b->firstA->string << ", " << b->secondA->string << std::endl;
}

A *c_getFirstA(B *b) {
    return b->firstA;
}

A *c_getSecondA(B *b) {
    return b->secondA;
}

}

测试'C lib'

void testA() {
    A *a = c_newA("An A");
    c_printA(a);
    c_freeA(a);
    // outputs: "An A"
    // valgrind is happy =]
}
void testB() {
    B *b = c_newB("first A", "second A");
    c_printB(b);
    c_freeB(b);
    // outputs: "first A, second A"
    // valgrind is happy =]
}

AB的包装程序类>
class AWrapper {

    struct deleter {
        void operator()(A *a) {
            c_freeA(a);
        }
    };

    std::unique_ptr<A, deleter> aptr_;
public:

    explicit AWrapper(A *a)
            : aptr_(a) {
    }

    static AWrapper fromString(const std::string &string) { // preferred way of instantiating
        A *a = c_newA(string.c_str());
        return AWrapper(a);
    }

    void printA() {
        c_printA(aptr_.get());
    }
};


class BWrapper {

    struct deleter {
        void operator()(B *b) {
            c_freeB(b);
        }
    };

    std::unique_ptr<B, deleter> bptr_;
public:
    explicit BWrapper(B *b)
            : bptr_(std::unique_ptr<B, deleter>(b)) {
    }

    static BWrapper fromString(const std::string &first, const std::string &second) {
        B *b = c_newB(first.c_str(), second.c_str());
        return BWrapper(b);
    }

    void printB() {
        c_printB(bptr_.get());
    }

    AWrapper getFirstA(){
        return AWrapper(c_getFirstA(bptr_.get()));
    }

    AWrapper getSecondA(){
        return AWrapper(c_getSecondA(bptr_.get()));
    }

};

包装器测试

void testAWrapper() {
    AWrapper a = AWrapper::fromString("An A");
    a.printA();
    // outputs "An A"
    // valgrind is happy =]
}

void testBWrapper() {
    BWrapper b = BWrapper::fromString("first A", "second A");
    b.printB();
    // outputs "first A"
    // valgrind is happy =]
}

问题的说明

很好,所以我继续开发完整的包装器(许多类),并意识到当这样的类(即聚合关系)都在范围内时,C ++会自动分别调用这两个类的描述符,但是由于基础库的结构(即对free的调用),我们遇到了内存问题:

void testUsingAWrapperAndBWrapperTogether() {
    BWrapper b = BWrapper::fromString("first A", "second A");
    AWrapper a1 = b.getFirstA();
    // valgrind no happy =[

}

Valgrind输出

enter image description here

我尝试过的事情

无法克隆

我尝试的第一件事是获取A的副本,而不是让他们尝试释放相同的A。对于我来说,这是一个好主意,但由于我所使用的库的性质而无法实现。实际上,这里有一个捕获机制,因此当您创建一个新的A并使用之前看到过的字符串时,它将带给您相同的ASee this question for my attempts at cloning A

自定义析构函数

我获取了C库析构函数的代码(此处为AfreeA,并将其复制到我的源代码中。然后,我尝试修改它们,以使A不会被B释放。这部分起作用。一些内存问题的实例已经解决,但是由于这种想法无法解决当前的问题(只是暂时掩盖了主要问题),因此不断出现新的问题,其中有些晦涩难懂且难以调试。

问题

因此,最后我们提出了一个问题:如何修改C ++包装器以解决由于底层C对象之间的交互而引起的内存问题?我可以更好地利用智能指针吗?我是否应该完全放弃C包装程序,而按原样使用库指针?还是有我没想到的更好的方法?

谢谢。

编辑:评论回复

由于问了上一个问题(上面已链接),我重构了代码,以便在与包装的库相同的库中开发和构建包装。因此,对象不再是不透明的。

指针是从对库的函数调用生成的,该库使用freeBcalloc进行分配。

在实码中,malloc是来自Araptor_uri*(typdef librdf_uri*),并分配有raptor2,而librdf_new_uriB(又名raptor_term*)并分配有librdf_node*librdf_new_node_* functions有一个librdf_node字段。

编辑2

我也可以指向代码行,如果相同的字符串,则返回相同的librdf_uri。参见A

我正在围绕面向对象的C库编写一个薄C ++包装器的问题。这个想法是要自动化内存管理,但是到目前为止,它并不是很自动。基本上,当我使用包装器时...

c++ c pointers memory valgrind
3个回答
1
投票

问题是line 137 heregetFirstA返回实例类型getSecondA的实例。这意味着在构造AWrapper时,您将放弃AWrapper的所有权,但是A *getFirstA不会这样做。构造返回的对象的指针由getFirstB管理。

最简单的解决方案是,您应该返回BWrapper而不是包装器类。这样,您就不会传递内部A *成员的所有权。我还建议使构造函数将包装器类中的指针设为私有,并使用类似于AfromPointer静态方法,该方法获取传递给它的指针的所有权。这样,您就不会意外地使用原始指针创建包装类的实例。


1
投票

您两次释放A


-1
投票

释放后设置为raptor_uri *,释放前测试它是否为NULL

NULL
© www.soinside.com 2019 - 2024. All rights reserved.