C 内存管理约定:通过在堆栈上分配的对象释放在堆上分配的内存
C memory management conventions: freeing memory allocated on heap by object allocated on stack
我觉得我对 C 中的内存管理约定有点困惑。
假设我们有一个在堆上动态分配数据的结构。
此结构提供 _alloc()
和 _free()
函数,用于在堆上分配和释放此结构。
这是一个简单的向量结构示例:
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;
}
现在假设我们不使用 _alloc()
或 _free()
而是决定在堆栈上分配此结构。
然后我们通过栈分配结构间接分配堆上的数据(my_vec.items
)
void main()
{
vec_t my_vec;
vec_push(&my_vec, 8); // allocates memory
// vec_destroy(&my_vec); // PROBLEM
return 0;
}
现在我们遇到了一个问题:我们不想释放堆栈上的结构 (my_vec
),但我们需要释放堆上的结构分配的数据 (my_vec.items
).
我认为这要么是设计问题,要么是约定问题,或者两者兼而有之。
我看到有人在 _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);
}
释放由 _deinit()
中的结构分配的内存是否有意义?
如果这种方法是正确的,我是否正确地说像这样在堆栈上分配的结构总是需要 _init()
和 _deinit()
?
如果您正在使用 _init
和 _deinit
函数,是的,您希望 _deinit
释放内存,是的,vec_init
和 vec_deinit
对于堆栈分配的结构是强制性的。对于这个用例,堆栈分配的结构 可以 初始化为 vec_t my_vec = {0};
并且避免了 vec_init
调用,但是假设清零现在和永远产生一个有效初始化的结构(如果您稍后更改 vec_init
以使某些字段非零,则未使用 vec_init
的图书馆用户必须更新),并且当不可避免的 vec_deinit
未与相应的 vec_init
.
配对
请注意,代码不需要重复得如此之多; _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);
}
我个人的做法是在设计中假设结构可以并且将会存在于堆栈中,并编写在已分配结构上工作的代码。快速简化示例:
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);
}
}
将结构放在堆栈上的优点是堆分配较少(有利于性能和碎片化),但当然这是以堆栈大小为代价的,这可能是您的限制,具体取决于您所处的环境进来了。
您混淆了两个概念:对象的动态内存分配和初始化。
考虑到这个结构声明
struct vec_t {
int *items;
size_t size;
size_t capacity;
};
没有说明这种类型的对象应该分配到堆中。
但是,应初始化与其定义位置无关的类型的对象。否则你会得到未定义的行为。
初始化的逆操作就是清理
您可以声明一个具有自动存储持续时间的类型的对象,如
struct vec_t v = { .items = NULL, .size = 0, .capacity = 0 };
但是这种方式并不灵活。用户可以直接访问实现/结构定义中的任何更改都可能导致此初始化不正确。
所以最好提供一个通用的接口来初始化一个类型的对象。你可以这样写
void vec_init( struct vec_t *v )
{
v->items = NULL;
v->size = 0;
v->capacity = 0;
}
和
void vec_clear( struct vec_t *vec )
{
free( v->items );
v->size = 0;
v->capacity = 0;
}
在 C 中,例如 C++,如果您正在动态分配对象,则不会自动调用其初始化(构造)。
所以如果你想为动态对象分配提供一个接口,你需要再写一个函数,例如
struct vec_t * vec_create( void )
{
struct vec_t *v = malloc( sizeof( *v ) );
if ( v != NULL ) vec_init( v );
return v;
}
在这种情况下,您可以向用户提供另一种功能来释放动态分配的对象,例如
void vec_destroy( struct vec_t **v )
{
free( *v );
*v = NULL;
};
我觉得我对 C 中的内存管理约定有点困惑。
假设我们有一个在堆上动态分配数据的结构。
此结构提供 _alloc()
和 _free()
函数,用于在堆上分配和释放此结构。
这是一个简单的向量结构示例:
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;
}
现在假设我们不使用 _alloc()
或 _free()
而是决定在堆栈上分配此结构。
然后我们通过栈分配结构间接分配堆上的数据(my_vec.items
)
void main()
{
vec_t my_vec;
vec_push(&my_vec, 8); // allocates memory
// vec_destroy(&my_vec); // PROBLEM
return 0;
}
现在我们遇到了一个问题:我们不想释放堆栈上的结构 (my_vec
),但我们需要释放堆上的结构分配的数据 (my_vec.items
).
我认为这要么是设计问题,要么是约定问题,或者两者兼而有之。
我看到有人在 _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);
}
释放由 _deinit()
中的结构分配的内存是否有意义?
如果这种方法是正确的,我是否正确地说像这样在堆栈上分配的结构总是需要 _init()
和 _deinit()
?
如果您正在使用 _init
和 _deinit
函数,是的,您希望 _deinit
释放内存,是的,vec_init
和 vec_deinit
对于堆栈分配的结构是强制性的。对于这个用例,堆栈分配的结构 可以 初始化为 vec_t my_vec = {0};
并且避免了 vec_init
调用,但是假设清零现在和永远产生一个有效初始化的结构(如果您稍后更改 vec_init
以使某些字段非零,则未使用 vec_init
的图书馆用户必须更新),并且当不可避免的 vec_deinit
未与相应的 vec_init
.
请注意,代码不需要重复得如此之多; _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);
}
我个人的做法是在设计中假设结构可以并且将会存在于堆栈中,并编写在已分配结构上工作的代码。快速简化示例:
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);
}
}
将结构放在堆栈上的优点是堆分配较少(有利于性能和碎片化),但当然这是以堆栈大小为代价的,这可能是您的限制,具体取决于您所处的环境进来了。
您混淆了两个概念:对象的动态内存分配和初始化。
考虑到这个结构声明
struct vec_t {
int *items;
size_t size;
size_t capacity;
};
没有说明这种类型的对象应该分配到堆中。
但是,应初始化与其定义位置无关的类型的对象。否则你会得到未定义的行为。
初始化的逆操作就是清理
您可以声明一个具有自动存储持续时间的类型的对象,如
struct vec_t v = { .items = NULL, .size = 0, .capacity = 0 };
但是这种方式并不灵活。用户可以直接访问实现/结构定义中的任何更改都可能导致此初始化不正确。
所以最好提供一个通用的接口来初始化一个类型的对象。你可以这样写
void vec_init( struct vec_t *v )
{
v->items = NULL;
v->size = 0;
v->capacity = 0;
}
和
void vec_clear( struct vec_t *vec )
{
free( v->items );
v->size = 0;
v->capacity = 0;
}
在 C 中,例如 C++,如果您正在动态分配对象,则不会自动调用其初始化(构造)。
所以如果你想为动态对象分配提供一个接口,你需要再写一个函数,例如
struct vec_t * vec_create( void )
{
struct vec_t *v = malloc( sizeof( *v ) );
if ( v != NULL ) vec_init( v );
return v;
}
在这种情况下,您可以向用户提供另一种功能来释放动态分配的对象,例如
void vec_destroy( struct vec_t **v )
{
free( *v );
*v = NULL;
};