结构中的共享对象:调用程序和库之间(在 c 中)

发布于 2024-07-30 04:49:50 字数 561 浏览 3 评论 0原文

在一个单独的库中,我们有一个结构体:

typedef struct bsmat{
int m;
int *n;
double **d;
} bs;

其中 **d 是指向双精度数组的指针数组。

bs *new_bs(int n, double **d);

有两种用例:

(a) 主应用程序分配多个双精度矩阵并调用库来构造结构。

b = new_bs(5, p)

(b) 或者,库可以通过调用函数来生成对象:

bs *add(bs *a, bs *b);

在第二种情况下,库拥有 **d 并可以在必要时释放它。 在第一种情况下,应用程序已分配数组,并且库可以读取/写入该数组。 我不清楚谁应该释放什么以及何时释放?

允许主应用程序拥有结构成员是否有问题? 这种方法有什么问题? 有哪些替代方案? 我们试图避免来回复制大量数组。

谢谢

TR

In a separate library, we have a struct with:

typedef struct bsmat{
int m;
int *n;
double **d;
} bs;

where **d is a array of pointers to double arrays.

bs *new_bs(int n, double **d);

There are two use cases:

(a) The main application allocates multiple double matrices and calls the library to construct the structure.

b = new_bs(5, p)

(b) Alternatively, an object may be generated by the library as a result of a call to a function:

bs *add(bs *a, bs *b);

In the second case, the library owns **d and can free it when necessary. In the first case, the application has allocated the array and the library may read from/write to it. It is not clear to me as to who should free what and when??

Is it a problem to allow the main application to own a struct-member? What are the problems with this approach? What are the alternatives? We are trying to avoid copying large number of arrays back-and-forth.

thanks

TR

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

谢绝鈎搭 2024-08-06 04:49:50

一般来说,图书馆记录其与使用其服务的应用程序签订的“合同”非常重要。 库编写者记录了库所做的所有分配以及应用程序需要释放的内容。 与任何合同一样,简单且一致的合同就是好的。

在这种特殊情况下,我认为图书馆还应该提供:

void free_bs(bs *b) 
{
     if (b->d) {
          /* free b->d here */
     }
     /* free rest of the structure */
}

释放结构。 在这里释放 d 数组也是有意义的。 应用程序具有指向所有 bs 结构的指针,并负责在不再需要时调用它们的 free_bs

例如,如果您想保留 d 以供将来使用,则在完成库操作后,合同会稍微复杂一些。 该库还可以提供:

double** bs_get_data(bs *b)
{
     double **d = b->d;
     b->d = NULL;
     return b->d;
}

返回一个指向 d 的指针,并将该字段留空,以便自由例程知道要跳过它。

bs_get_data() 的文档必须解释它只能被调用一次,并且在这种情况下应用程序负责释放数组。

更新:
回答您下面的评论:首先,请注意,我通过假设(至少)以下内容简化了问题:d 由单个 bs 引用结构或应用程序。 应用程序和库将对数组的单个引用从一个传递到另一个。 例如,如果您希望在更多 bs 结构中使用相同的 d 数组,那么我的方法还不够。

您在评论中提出的标志可能会有所帮助,但不是标准做法,FWIK。 在这种情况下,我建议实现一些简单的引用计数。 如果 d 是需要共享的资源,请将其设为“对象”:

 typedef struct {
         double **d;
         int ref;
 } d_t;

new_bs() 中将 ref 初始化为 1。在所有接收d“副本”的函数中,增加引用计数。 当 bs 结构被删除时,或者当您的应用程序中不再需要 d 时,请减少它的引用计数。 如果它为零,则释放它。 在某种程度上,这就是高级语言为您所做的事情,并且在保持资源管理理智方面非常有效。

因此没有人拥有该数组,但最后需要它的人可以释放它。

当然,这需要更多的工作并使代码变得复杂,因此请尽量保持平衡。 并不是您拥有的每个结构都需要它,而只是您需要保留多个引用的结构才需要它。

In general, it's important that the library documents the 'contract' it makes with the applications that are using its service. The library writer documents all the allocations made by the library and what needs to be freed by the application. Like any contract, it's good when it is simple and consistent.

In this particular case, I suppose the library should also offer:

void free_bs(bs *b) 
{
     if (b->d) {
          /* free b->d here */
     }
     /* free rest of the structure */
}

which frees the structure. It makes sense to also free the d array here. The application has pointer to all the bs structures and is responsible for calling free_bs on them when no longer needed.

If, for example, you want to keep d for future usage, after you're done with the library operation, the contract is a bit more complex. The library could also provide:

double** bs_get_data(bs *b)
{
     double **d = b->d;
     b->d = NULL;
     return b->d;
}

which returns a pointer to d, and leaves the field empty so that the free routine knows to skip it.

The docs for the bs_get_data() have to explain that it can be called only once, and that the application takes the responsibility of freeing the array in this case.

UPDATE:
In answer to your comment below: First of all, please note that I simplified the problem by assuming (at least) the following: the d is referenced either by a single bs structure or by the application. The application and the library "pass" a single reference to the array from one to the other. If you want the same d array in more bs structures, for example, than my approach is not enough.

The flag that you propose in the comment might help, but is not standard practice, FWIK. In this case, I would suggest to implement some simple reference counting. If d is the resource that needs to be shared around, make it an "object":

 typedef struct {
         double **d;
         int ref;
 } d_t;

Initialize ref with 1. In new_bs() and in all functions that receive a "copy" of d, increment the reference count. When the bs structure gets deleted, or when you no longer need d in your application, decrement it's reference count. If it gets to zero, free it. In a way, it is what the high level languages do for you, and is very efficient in keeping the resource management sane.

So no one is owning the array, but whoever needs it last, frees it.

Of course, this requires more work and complicates code, so try to put in balance. It's not needed for every structure you have, but only for those you need to keep multiple references to.

魄砕の薆 2024-08-06 04:49:50

我认为引用计数对于这个问题来说太过分了。 但是您的界面需要更准确地指定谁拥有 *b 而不仅仅是 b->d。 我根本不清楚图书馆如何知道何时“有必要”(如您所说)释放b->d

我将按如下方式扩展接口:

  1. 每个 bs * 类型的指针要么外部管理,要么内部管理。 函数new_bs返回一个外部管理的指针; 函数 add 返回一个内部管理的指针。

  2. Every 返回 bs * 类型指针的函数应该说明结果的管理是内部还是外部。

  3. 你可能应该提供函数

    void bs_free_ext(bs **);   // 释放 *bs 并设置 *bs = NULL(外部管理) 
      无效bs_free_int(bs **);   // 释放 *bs 并设置 *bs = NULL(内部管理) 
      

    bs_free_ext 仅释放*bsbs_free_int 释放 *bs(*bs)->d

  4. 为了安全起见,我还会添加记录每个结构的存储管理的字段,并且我会对任何分配或释放的函数进行断言。 (原因:与分配或释放的成本相比,断言检查便宜。)

如果您可以在 Linux 上运行应用程序,则使用 进行内存检查valgrind 是一个优点。

PS 内存管理的细节泄漏到几乎每个接口中这一事实是我们为了获得 C 语言编程的好处而付出的代价之一。

I think reference counting is overkill for this problem. But your interface needs to specify more precisely who owns *b and not just b->d. It's not at all clear to me how the library knows when it is "necessary" (as you put it) to free b->d.

I'd extend the interface as follows:

  1. Every pointer of type bs * is either externally managed or internally managed. Function new_bs returns an externally managed pointer; function add returns an internally managed one.

  2. Every function that returns a pointer of type bs * should say whether the management of the result is internal or external.

  3. You probably should provide functions

    void bs_free_ext(bs **);  // free *bs and set *bs = NULL (external mgmt)
    void bs_free_int(bs **);  // free *bs and set *bs = NULL (internal mgmt)
    

    bs_free_ext frees only *bs; bs_free_int frees both *bs and (*bs)->d.

  4. For safety's sake I would also add the field that records the storage management of each struct, and I would have assertions on any function that allocates or frees. (Reason: assertion checking is cheap compared to the cost of allocating or freeing.)

If you can run your app on Linux then memory checking with valgrind is a plus.

P.S. The fact that details of memory management leak into almost every interface is one of the costs we pay to get the benefits of programming in C.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文