общие объекты в структуре: между вызывающей программой и библиотекой (в c) - PullRequest
1 голос
/ 06 августа 2009

В отдельной библиотеке у нас есть структура с:

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

Ответы [ 2 ]

3 голосов
/ 06 августа 2009

В целом, важно, чтобы библиотека документировала «контракт», который она заключает с приложениями, которые используют ее сервис. Автор библиотеки документирует все выделения, сделанные библиотекой, и то, что должно быть освобождено приложением. Как и любой контракт, хорошо, когда он прост и последовательн.

В этом конкретном случае, я полагаю, библиотека также должна предлагать:

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() должны объяснить, что он может быть вызван только один раз, и что в этом случае приложение берет на себя ответственность за освобождение массива.

UPDATE: В ответ на ваш комментарий ниже: Прежде всего, обратите внимание, что я упростил проблему, предполагая (по крайней мере) следующее: на d ссылается либо одна bs структура, либо приложение. Приложение и библиотека «передают» одну ссылку на массив от одного к другому. Например, если вам нужен один и тот же массив d в большем количестве bs структур, то моего подхода недостаточно.

Флаг, который вы предлагаете в комментарии, может помочь, но это не стандартная практика, FWIK. В этом случае я бы предложил реализовать простой подсчет ссылок . Если d - это ресурс, которым нужно делиться, сделайте его «объектом»:

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

Инициализировать ref с помощью 1. В new_bs() и во всех функциях, которые получают «копию» d, увеличить счетчик ссылок. Когда структура bs будет удалена или когда вам больше не понадобится d в вашем приложении, уменьшите счетчик ссылок. Если он достигнет нуля, освободите его. В некотором смысле, это то, что языки высокого уровня делают для вас, и очень эффективно поддерживают управление ресурсами в здравом уме.

То есть массив не принадлежит никому, но тот, кто в нем нуждается последний, освобождает его.

Конечно, это требует больше работы и усложняет код, поэтому постарайтесь найти баланс. Он не нужен для каждой имеющейся у вас структуры, но только для тех, на которые нужно хранить несколько ссылок.

1 голос
/ 06 августа 2009

Я думаю, что подсчет ссылок является излишним для этой проблемы.Но ваш интерфейс должен более точно указывать, кому принадлежит *b, а не просто b->d.Мне не совсем понятно, как библиотека знает, когда «необходимо» (как вы выразились) освободить b->d.

Я бы расширил интерфейс следующим образом:

  1. Каждый указатель типа bs * имеет либо внешнее управление , либо внутреннее управление .Функция new_bs возвращает управляемый извне указатель;Функция add возвращает внутреннее управление.

  2. Каждая функция , которая возвращает указатель типа bs *, должна указывать, является ли управление результатом внутренним илиexternal.

  3. Вы, вероятно, должны предоставить функции

    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 освобождает только *bs;bs_free_int освобождает как *bs, так и (*bs)->d.

  4. В целях безопасности я бы также добавил поле, которое записывает управление хранением каждой структуры, и у меня будут утверждения на любойфункция, которая выделяет или освобождает.(Причина: проверка утверждений обходится дешевле по сравнению со стоимостью выделения или освобождения.)

Если вы можете запустить свое приложение в Linux, тогда проверка памяти с помощью valgrind являетсяplus.

PS Тот факт, что детали управления памятью проникают почти в каждый интерфейс, является одной из затрат, которую мы платим, чтобы получить преимущества программирования на C.

...