如何解决由 C++ 包装器中的 C 对象之间的交互引起的与内存相关的错误? [英] How to resolve memory related errors that arise from interaction between C objects in a C++ wrapper?

查看:55
本文介绍了如何解决由 C++ 包装器中的 C 对象之间的交互引起的与内存相关的错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

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

C 库的最小例子

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

#include #include cstring"#include iostream"外部C"{类型定义结构{无符号字符*字符串;} 一种;A *c_newA(const char *string) {A *a = (A *) malloc(sizeof(A));//是的,我知道,不要在 C++ 中使用 malloc.这是一个模拟使用它的 C 库的演示.auto *s = (char *) malloc(strlen(string) + 1);strcpy(s, string);a->string = (unsigned char *) s;返回一个;}void c_freeA(A *a) {自由(a->字符串);免费(一);}void c_printA(A *a) {std::cout <<a->字符串<<std::endl;}类型定义结构{A *第一个A;A *秒A;乙;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);返回 b;}void c_freeB(B *b) {c_freeA(b->firstA);c_freeA(b->secondA);免费(b);}void c_printB(B *b) {std::cout <<b->firstA->字符串<<","<<b->secondA->字符串<<std::endl;}A *c_getFirstA(B *b) {返回 b->firstA;}A *c_getSecondA(B *b) {返回 b->secondA;}}

测试C 库"

void testA() {A *a = c_newA(An A");c_printA(a);c_freeA(a);//输出:一个 A"//valgrind 很高兴 =]}

void testB() {B *b = c_newB(第一个A",第二个A");c_printB(b);c_freeB(b);//输出:第一个 A,第二个 A"//valgrind 很高兴 =]}

AB

的包装类

类包装器{结构删除器{void operator()(A *a) {c_freeA(a);}};std::unique_ptraptr_;上市:显式包装器(A *a): aptr_(a) {}static AWrapper fromString(const std::string &string) {//首选的实例化方式A *a = c_newA(string.c_str());返回包装器(a);}无效打印A(){c_printA(aptr_.get());}};类 BWrapper {结构删除器{void 运算符()(B *b) {c_freeB(b);}};std::unique_ptrbptr_;上市:显式 BWrapper(B *b): bptr_(std::unique_ptr(b)) {}静态 BWrapper fromString(const std::string &first, const std::string &second) {B *b = c_newB(first.c_str(), second.c_str());返回 BWrapper(b);}无效的打印B(){c_printB(bptr_.get());}包装器 getFirstA(){返回 AWrapper(c_getFirstA(bptr_.get()));}包装器 getSecondA(){返回 AWrapper(c_getSecondA(bptr_.get()));}};

包装测试

<预><代码>无效 testAWrapper() {AWrapper a = AWrapper::fromString("An A");a.printA();//输出An A"//valgrind 很高兴 =]}void testBWrapper() {BWrapper b = BWrapper::fromString("first A", "second A");b.printB();//输出第一个 A"//valgrind 很高兴 =]}

问题演示

太好了,所以我继续开发完整的包装器(很多类)并意识到当这样的类(即聚合关系)都在作用域内时,C++ 会自动分别调用两个类的析构函数,但由于底层库的结构(即对free的调用),我们得到了内存问题:

void testUsingAWrapperAndBWrapperTogether() {BWrapper b = BWrapper::fromString("first A", "second A");包装器 a1 = b.getFirstA();//valgrind 不开心 =[}

Valgrind 输出

我尝试过的事情

无法克隆

我尝试的第一件事是复制A,而不是让他们尝试释放相同的A.这虽然是个好主意,但由于我正在使用的库的性质,在我的情况下是不可能的.实际上有一个捕获机制,因此当您使用以前看到的字符串创建新的 A 时,它会返回相同的 A.有关我尝试克隆 A 的尝试,请参阅此问题.

自定义析构函数

我将 C 库析构函数的代码(freeAfreeB 在这里)复制到我的源代码中.然后我尝试修改它们,使 A 不会被 B 释放.这部分起作用了.内存问题的一些实例已经解决,但由于这个想法没有解决手头的问题(只是暂时掩盖了主要问题),新问题不断出现,其中一些是模糊的,难以调试.

问题

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

提前致谢.

回复评论

自从提出上一个问题(上面链接)后,我重构了我的代码,以便包装器正在与它包装的库中开发和构建在同一个库中.所以对象不再是不透明的.

指针是从函数调用到库生成的,库使用callocmalloc来分配.

在真正的代码中,Araptor_uri* (typdef librdf_uri*) 来自 raptor2 并且被分配了librdf_new_uriBraptor_term*(又名 librdf_node*)并分配有 librdf_new_node_* 函数.librdf_node 有一个 librdf_uri 字段.

编辑 2

我还可以指向返回相同 A 的代码行,如果它是相同的字符串.请参阅 此处的第 137 行

解决方案

问题是 getFirstAgetSecondA 返回 AWrapper 的实例,这是一种拥有类型.这意味着在构建 AWrapper 时,您放弃了 A * 的所有权,但是 getFirstAgetFirstB> 不要那样做.构造返回对象的指针由 BWrapper 管理.

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

如果您想避免使用原始指针或希望对从 getFirstAgetSecondA 返回的对象具有方法,您可以编写一个简单的引用包装器,它有一个原始的指针作为成员.

class AReference{私人的:A *a_ref_;上市:显式 AReference(A *a_ref) : a_ref_(a_ref) {}//这里的其他方法,例如打印或获取};

The problem

I am writing a thin C++ wrapper around an object oriented C library. The idea was to automate memory management, but so far its not been very automatic. Basically when I use my wrapper classes, I get all kinds of memory access and inappropriate freeing problems.

Minimal example of C library

Lets say the C library consists of A and B classes, each of which have a few 'methods' associated with them:

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

}

Test the '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 =]
}

Wrapper classes for A and B

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

};

Wrapper tests


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 =]
}

Demonstration of the problem

Great, so I move on and develop the full wrapper (lot of classes) and realise that when classes like this (i.e. aggregation relationship) are both in scope, C++ will automatically call the descructors of both classes separately, but because of the structure of the underlying library (i.e. the calls to free), we get memory problems:

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

}

Valgrind output

Things I've tried

Cloning not possible

The first thing I tried was to take a copy of A, rather than having them try to free the same A. This, while a good idea, is not possible in my case because of the nature of the library I'm using. There is actually a catching mechanism in place so that when you create a new A with a string its seen before, it'll give you back the same A. See this question for my attempts at cloning A.

Custom destructors

I took the code for the C library destructors (freeA and freeB here) and copied them into my source code. Then I tried to modify them such that A does not get freed by B. This has partially worked. Some instances of memory problems have been resolved, but because this idea does not tackle the problem at hand (just kind of temporarily glosses over the main issue), new problems keep popping up, some of which are obscure and difficult to debug.

The question

So at last we arive at the question: How can I modify this C++ wrapper to resolve the memory problems that arise due to the interactions between the underlying C objects? Can I make better use of smart pointers? Should I abandon the C wrapper completly and just use the libraries pointers as is? Or is there a better way I haven't thought of?

Thanks in advance.

Edits: response to the comments

Since asking the previous question (linked above) I have restructed my code so that the wrapper is being developed and built in the same library as the one it wraps. So the objects are no longer opaque.

The pointers are generated from function calls to the library, which uses calloc or malloc to allocate.

In the real code A is raptor_uri* (typdef librdf_uri*) from raptor2 and is allocated with librdf_new_uri while B is raptor_term* (aka librdf_node*) and allocated with librdf_new_node_* functions. The librdf_node has a librdf_uri field.

Edit 2

I can also point to the line of code where the same A is returned if its the same string. See line 137 here

解决方案

The problem is that getFirstA and getSecondA return instances of AWrapper, which is an owning type. This means that when constructing an AWrapper you're giving up the ownership of an A *, but getFirstA and getFirstB don't do that. The pointers from which the returned objects are constructed are managed by a BWrapper.

The easiest solution is that you should return an A * instead of the wrapper class. This way you're not passing the ownership of the inner A member. I also would recommend making the constructors taking pointers in the wrapper classes private, and having a fromPointer static method similar to fromString, which takes ownership of the pointer passed to it. This way you won't accidently make instances of the wrapper classes from raw pointers.

If you want to avoid using raw pointers or want to have methods on the returned objects from getFirstA and getSecondA you could write a simple reference wrapper, which has a raw pointer as a member.

class AReference
{
private:
    A *a_ref_;
public:
    explicit AReference(A *a_ref) : a_ref_(a_ref) {}

    // other methods here, such as print or get

};

这篇关于如何解决由 C++ 包装器中的 C 对象之间的交互引起的与内存相关的错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆