简体   繁体   English

C memory 管理约定:释放 memory 分配在堆上的 ZA8CFZZA8CFDE6331BD59EB2AC96F8911 分配的堆栈

[英]C memory management conventions: freeing memory allocated on heap by object allocated on stack

I think I'm a bit confused with memory management conventions in C.我想我对 C 中的 memory 管理约定有点困惑。

Let's say we have a struct that dynamically allocate data on the heap.假设我们有一个在堆上动态分配数据的结构。 This struct provides _alloc() and _free() functions for allocating and freeing this struct on the heap.该结构提供_alloc()_free()函数用于在堆上分配和释放该结构。

Here's a example with a simple vector struct:这是一个带有简单向量结构的示例:

struct vec_t {
  int *items;
  size_t size;
  size_t capacity;
};

struct vec_t *vec_alloc(void)
{
  struct vec_t *vec = malloc(sizeof(struct vec_t));

  vec->items = NULL;
  vec->size = 0;
  vec->capacity = 0;

  return vec;
}

void vec_free(struct vec_t *vec)
{
  free(vec->items);
  free(vec);
}

void vec_push(struct vec_t *vec, int item)
{
  if (vec->size == vec->capacity)
  {
    size_t new_capacity = vec->capacity > 0 ? (vec->capacity + 1) * 2 : 5;
    vec->items = realloc(vec->items, new_capacity * sizeof(int));
  }
  vec->items[vec->size++] = item;
}

Now let's assume we don't use _alloc() or _free() and instead decide to allocate this struct on the stack.现在让我们假设我们不使用_alloc()_free()而是决定在堆栈上分配这个结构。

We then indirectly allocate data on the heap via the stack allocated struct ( my_vec.items )然后我们通过堆栈分配的结构( my_vec.items )间接在堆上分配数据

void main()
{
  vec_t my_vec;
  vec_push(&my_vec, 8); // allocates memory

  // vec_destroy(&my_vec); // PROBLEM

  return 0;
}

Now we have a problem: we don't want to free the struct ( my_vec ) as it's on the stack, but we need to free the data allocated by the struct on the heap ( my_vec.items ).现在我们有一个问题:我们不想释放堆栈上的结构( my_vec ),但我们需要释放堆上的结构( my_vec.items )分配的数据。

I believe this is either a design issue or a convention issue, or a mix of both.我相信这要么是设计问题,要么是约定问题,或者两者兼而有之。

I've seen people add some extra functions _init() and _deinit() in addition to _alloc() and _free() .除了_alloc()_free()之外,我还看到人们添加了一些额外的函数_init()_deinit() ) 。

void vec_init(struct vec_t *vec)
{
  vec->items = NULL;
  vec->size = 0;
  vec->capacity = 0;
}

void vec_deinit(struct vec_t *vec)
{
  free(vec->items);
}

Would it make sense to free memory allocated by the struct in _deinit() ?释放_deinit()中的结构分配的 memory 是否有意义?

If this approach is corret, am I correct saying that a struct allocated on the stack like this always need to be _init() and _deinit() ?如果这种方法是正确的,我是否正确地说像这样分配在堆栈上的结构总是需要是_init()_deinit()

If you're using _init and _deinit functions, yes, you'd want _deinit to free the memory, and yes, vec_init and vec_deinit would be mandatory for stack allocated structs.如果您使用_init_deinit函数,是的,您希望_deinit释放 memory,是的, vec_initvec_deinit对于堆栈分配的结构是必需的。 For this use case, a stack allocated struct could be initialized with vec_t my_vec = {0};对于这个用例,可以使用vec_t my_vec = {0};初始化堆栈分配的结构。 and a vec_init call avoided, but that assumes zeroing produces a validly initialized struct now and forever (if you change vec_init later to make some fields non-zero, users of your library that didn't use vec_init have to update), and it can be confusing when the unavoidable vec_deinit is not paired with a corresponding vec_init .并且避免了vec_init调用,但假设归零现在和永远产生一个有效初始化的结构(如果您稍后更改vec_init以使某些字段非零,则未使用vec_init的库的用户必须更新),并且它可以当不可避免的vec_deinit没有与相应的vec_init配对时会令人困惑。

Note that code need not be so heavily duplicated;请注意,代码不需要如此重复; _alloc and _free can be implemented in terms of _init and _deinit , keeping the code duplication to a minimum: _alloc_free可以根据_init_deinit来实现,将代码重复降至最低:

struct vec_t *vec_alloc(void)
{
  struct vec_t *vec = malloc(sizeof(struct vec_t));
  if (vec) vec_init(vec);  // Don't try to init if malloc failed
  return vec;
}

void vec_free(struct vec_t *vec)
{
  if (vec) vec_deinit(vec); // Don't try to deinit when passed NULL
  free(vec);
}

My personal approach to this is to assume in the design that the structure can and will live on the stack, and write code that work on an already allocated structure.我个人的方法是在设计中假设结构可以并且将存在于堆栈中,并编写在已经分配的结构上工作的代码。 Quick simplified example:快速简化示例:

typedef struct vect_t {
   char *data;
   size_t len;
} vec_t;

void vec_set(vec_t *v, void *data, size_t len) {
    v->data = data;
    v->len = len;
}

void vec_clear(vec_t *v) {
    free(v->data);
    vec_set(v, NULL, 0);
}

int vec_resize(vec_t *v, size_t len) {
    void * data = realloc(v->data, len);
    if (!data) { /* out of memory */
        vec_set(v, NULL, 0);
        return ENOMEM;
    }
    vec_set(v, data, len);
    return 0;
}

int stack_example(void) {
    vec_t v;
    int err;
    vec_set(&v, NULL, 0);
    if ((err = vec_resize(&v, 64)) !=0) {
        return err;
    }
    strcpy(v.data, "Hello World");
    vec_clear(&v);
    return 0;
}

void heap_example(void) {
    vec_t *v = malloc(sizeof(vec_t));
    if (v) {
        int err;
        vec_set(v, NULL, 0);
        if ((err = vec_resize(v, 64)) !=0) {
            return err;
        }
        strcpy(v->data, "Hello World");
        vec_clear(v);
        free(v);
   }
}

The advantage of having the structures on the stack is that you have fewer heap allocations (good for performance and fragmentation), but of course that's at the cost of stack size, which may be your limit depending on the environment you're in.将结构放在堆栈上的优点是您的堆分配更少(有利于性能和碎片),但当然这是以堆栈大小为代价的,这可能是您的限制,具体取决于您所处的环境。

You are mixing two concepts: dynamic memory allocation and initialization of an object.您混合了两个概念:动态 memory 分配和 object 的初始化。

Taking into account this structure declaration考虑到这个结构声明

struct vec_t {
  int *items;
  size_t size;
  size_t capacity;
};

nothing says that an object of this type shall be allocated in the heap.没有说应该在堆中分配这种类型的 object。

However the object of the type independent on where it is defined shall be initialized.然而,独立于其定义位置的类型的 object 应被初始化。 Otherwise you can get undefined behavior.否则你会得到未定义的行为。

The reverse operation of initialization is cleaning.初始化的逆向操作是清洗。

You could declare an object of the type with the automatic storage duration like您可以声明具有自动存储持续时间的类型的 object,例如

struct vec_t v = { .items = NULL, .size = 0, .capacity = 0 };

However such an approach is not flexible.然而,这种方法并不灵活。 The user has a direct access to the implementation/ Any changes in the structure definition can make this initialization incorrect.用户可以直接访问实现/结构定义中的任何更改都可能导致此初始化不正确。

So it is better to provide a general interface for initialization of an object of the type.所以最好提供一个通用接口来初始化一个object类型的。 You could write for example你可以写例如

void vec_init( struct vec_t *v )
{
    v->items = NULL;
    v->size = 0;
    v->capacity = 0;
}  

and

void vec_clear( struct vec_t *vec )
{
    free( v->items );
    v->size = 0;
    v->capacity = 0;
}

In C opposite to for example C++ if you are allocating an object dynamically its initialization (construction) is not called automatically.在 C 中,例如 C++ 如果动态分配 object,则不会自动调用其初始化(构造)。

So if you want to provide an interface for the dynamic object allocation you need to write one more function as for example所以如果你想为动态 object 分配提供一个接口,你需要再写一个 function 例如

struct vec_t * vec_create( void )
{
    struct vec_t *v = malloc( sizeof( *v ) );

    if ( v != NULL ) vec_init( v );

    return v;
}

In this case you can provide to the user one more function that frees the dynamically allocated object like在这种情况下,您可以向用户再提供一个 function 来释放动态分配的 object,例如

void vec_destroy( struct vec_t **v )
{
    free( *v );
    *v = NULL;
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM