删除资源时,最后编程崩溃。 C中伪泛型动态数组列表的实现

Programming crashing at the end, when deleting the resources. Implementation of pseudo-generic dynamic array list in C

我已经在 C 中为原始类型实现了一个伪泛型动态数组。它采用枚举常量作为每种类型的标识符 - INTLONG_INTDECIMALCHAR。主结构 list 有一个成员 _assets,它是一个 void 指针(具有抽象级别)。这个 void 指针在 C 文件实现中被类型转换为另一个结构(这是容器的主要内部工作)。我测试了所有类型的列表,它工作得很好。当我创建 2 个列表,执行一些操作,然后按照创建它们的相同顺序将这两个列表一起删除时,问题就出现了。它给了我一个错误:- Critical error detected c0000374;并说,unable to open 'free-base.cpp'。删除通过 free_assets 方法进行。当我在使用第二个列表对象之前删除第一个列表对象时,这两个列表工作得很好,这很不寻常,因为它们是两个不同的对象。

primitive_list.h

#ifndef PRIMITIVE_LIST_H 
#define PRIMITIVE_LIST_H 

typedef enum { INT, LONG_INT, DECIMAL, CHAR } list_types; 

typedef struct 
{
    void *_assets;
} list; 

list *create_list(list_types type);
void push(list **self, ...); 
void pop(list **self); 
void*at(list *self, const int index); 
int empty(list *self); 
unsigned length(list *self);
void print_list(list *self);
void free_assets(list **self);
#endif 

primitive_list.c

#include <stdio.h>
#include <stdlib.h> 
#include <stdarg.h>
#include "primitive_list.h"

typedef struct 
{
    int capacity; 
    int size; 
    void *arr; 
    list_types type; 
    int empty; 
} _list;

_list _create_list(list_types type)
{
    _list res; 
    res.type = type; 
    res.capacity = 1; 
    res.size = 0; 
    res.empty = 1; 
    return res;
}

void _alloc(_list *self)
{
    switch ((self)->type)
    {
        int n = (self)->capacity;
        case INT:
            (self)->arr = calloc(n, sizeof(int)); 
            break; 
        case LONG_INT:
            (self)->arr = calloc(n, sizeof(long long)); 
            break;
        case DECIMAL:
            (self)->arr = calloc(n, sizeof(double));
            break; 
        case CHAR:
            (self)->arr = calloc(n, sizeof(char));
            break;
    }
    return;
}

void _realloc(_list *self, size_t Buffer_size)
{
    (self)->capacity = Buffer_size; 
    list_types type = (self)->type;
    int n = (self)->capacity; 
    int s = (self)->size;
    if (type == INT) {
        int *new_array = (int *)calloc(n, sizeof(int));
        for (size_t i = 0; i < s; i++)
        {
            new_array[i] = ((int *)(self)->arr)[i];
        }
        free((self)->arr); 
        (self)->arr = (void *)new_array;
    } else if (type == LONG_INT) {
        long long *new_array = (long long *)calloc(n, sizeof(long long));
        for (size_t i = 0; i < s; i++)
        {
            new_array[i] = ((long long *)(self)->arr)[i];
        }
        free((self)->arr); 
        (self)->arr = (void *)new_array;
    } else if (type == DECIMAL) {
        double *new_array = (double *)calloc(n, sizeof(double));
        for (size_t i = 0; i < s; i++)
        {
            new_array[i] = ((double *)(self)->arr)[i];
        }
        free((self)->arr); 
        (self)->arr = (void *)new_array;
    } else if (type == CHAR) {
        char *new_array = (char *)calloc(n, sizeof(char));
        for (size_t i = 0; i < s; i++)
        {
            new_array[i] = ((char *)(self)->arr)[i];
        }
        free((self)->arr); 
        (self)->arr = (void *)new_array;
    }
    return;
}

void _push(_list *self, ...)
{
    if ((self)->empty)
    {
        (self)->empty = 0; 
        _alloc(self); 
    }

    if ((self)->size == (self)->capacity)
        _realloc(self, (self)->capacity * 2); 
    
    va_list arg; 
    va_start(arg, self); 
    switch ((self)->type)
    {
        case INT:
            ((int *)(self)->arr)[(self)->size] = va_arg(arg, int);
            break; 
        case LONG_INT:
            ((long long *)(self)->arr)[(self)->size] = va_arg(arg, long long);
            break; 
        case DECIMAL:
            ((double *)(self)->arr)[(self)->size] = va_arg(arg, double);
            break; 
        case CHAR:
            ((char *)(self)->arr)[(self)->size] =(char)va_arg(arg, int);
            break; 
    }
    (self)->size++;
    va_end(arg); 
    return;
}

void _pop(_list *self)
{
    if ((self)->empty)
    {
        fprintf(stderr,"List is empty!\n"); 
        return;
    }
    (self)->size--;
    return;
}

void *_at(_list *self, const int index)
{
    void *res; 
    switch ((self)->type)
    {
        case INT:
            res = malloc(sizeof(int)); 
            *((int *)res) = ((int *)(self)->arr)[index];
            break; 
        case LONG_INT:
            res = malloc(sizeof(long long)); 
            *((long long *)res) = ((long long *)(self)->arr)[index];
            break;
        case DECIMAL:
            res = malloc(sizeof(double)); 
            *((double *)res) = ((double *)(self)->arr)[index];
            break; 
        case CHAR:
            res = malloc(sizeof(char)); 
            *((char *)res) = ((char *)(self)->arr)[index];
            break;
    }
    return res;
}

int _empty(_list *self)
{
    return self->empty;
}

unsigned _length(_list *self)
{
    return self->size;
}

void _print_list(_list *self)
{
    for (size_t i = 0; i < self->size; i++)
    {
        switch(self->type)
        {
            case INT:
                printf("%d ", ((int *)self->arr)[i]);
                break;
            case LONG_INT:
                printf("%lld ", ((long long *)self->arr)[i]);
                break;
            case DECIMAL:
                printf("%lf ", ((double *)self->arr)[i]);
                break;
            case CHAR:
                printf("%c ",((char*)self->arr)[i]);
                break;
        }
    }
    printf("\n");
    return;
}

void _free_list(_list *self)
{
    free((self)->arr);
}

list *create_list(list_types type)
{
    static list res; 
    _list obj = _create_list(type); 
    res._assets = malloc(sizeof(_list)); 
    *((_list*)res._assets) = obj;
    return &res; 
}

void push(list **self, ...)
{
    va_list arg; 
    va_start(arg, self); 
    
    switch (((_list *)(*self)->_assets)->type)
    {
        case INT:
            _push(((_list *)(*self)->_assets), va_arg(arg, int));
            break;
        case LONG_INT:
            _push(((_list *)(*self)->_assets), va_arg(arg, long long));
            break; 
        case DECIMAL:
            _push(((_list *)(*self)->_assets), va_arg(arg, double));
            break; 
        case CHAR:
            _push(((_list *)(*self)->_assets), (char)va_arg(arg, int));
            break; 
    }
    va_end(arg); 
    return;
}

void pop(list **self)
{
    _pop(((_list *)(*self)->_assets));
    return;
}

void *at(list *self, const int index)
{
    return _at((_list *)self->_assets, index);
}

int empty(list *self)
{
    return _empty((_list *)self->_assets);
}

unsigned length(list *self)
{
    return _length((_list *)self->_assets);
}

void print_list(list *self)
{
    _print_list((_list *)self->_assets);
    return;
}

void free_assets(list **self)
{
    _free_list(((_list *)(*self)->_assets)); 
    free((*self)->_assets);
}

test.c

#include <stdio.h> 
#include <stdlib.h> 
#include "primitive_list.h"

int main(int argc, char **argv)
{
    //Decimal List 
    list *nums = create_list(DECIMAL); 
    push(&nums, 3.14159); 
    push(&nums, 6.25); 
    push(&nums, 22.2222); 
    push(&nums, 100.0);
    print_list(nums); 
    
    //Character list 
    list *chars = create_list(CHAR); 
    push(&chars, 'A');
    push(&chars, 'w');
    push(&chars, 'Z');
    push(&chars, 'q');
    push(&chars, 'P');
    print_list(chars); 
    
    //Code causing the error
    free_assets(&nums); 
    free_assets(&chars);
    return 0;
}

你这里有问题:

list*create_list(list_types type)
{
    static list res; 
    _list obj=_create_list(type); 
    res._assets=malloc(sizeof(_list)); 
    *((_list*)res._assets)=obj;
    return &res; 
}

这个函数在 main() 中被调用了两次,但是 return 是一个指向静态变量的指针,这意味着它每次被调用时都会 return 相同的地址。所以当 main() 看起来创建指向两个不同列表的指针时,它们实际上是指向单个列表的指针,即 static list res.

您的代码有点混乱,因为您似乎无缘无故地将底层结构掩埋在另一个结构中。没有理由你不能简单地(如果你想将实现细节保密)在 header 中写 typedef struct list list; 并且在你的实现中有

struct list {
    int capacity; 
    int size; 
    void *arr; 
    list_types type; 
    int empty;
};

这样,处理 _assets 指针的困惑就会少很多;您可以简单地直接处理列表成员而无需取消引用和转换(这是不必要的混淆)。

这里的主要问题实际上是由于 create_list 函数的实现导致内存管理不善。具体来说,这两行:

list *create_list(list_types type)
{
    static list res; 
    
    /* ... */
    
    return &res; 
}

static 关键字在这里的作用是定义一个在该函数的所有调用之间共享的全局变量。也就是说,每次调用 create_list 时,res 变量都会设置为与函数最后一次 return 时相同的值。这就是为什么你可以从这个函数 return &res 得到函数外部的有效指针:res 变量存储在程序的全局内存中而不是函数的堆栈帧中。

此程序存在(至少!)两个问题:

  1. 您创建的每个 list 的指针都是相同的,因此创建的每个 list 都会覆盖最后一个 list 的内存,这不仅会泄漏内存(指针_assets 结构),但仅使最后创建的列表有效。事实上,您的 numschars 列表应该具有完全相同的值 chars is created.
  2. 您在未使用 malloc 分配的内存上调用 free。因为您 return 来自 create_list 函数的 bona-fide 全局变量的地址,所以您 return 指向操作系统创建的内存的指针,当您的程序第一次运行时加载。您不拥有此内存,并且尝试对其调用 free 与您在此处看到的效果完全相同:它会使您的程序崩溃。

这两个问题都可以通过相同的方式解决:正确管理您的内存。一般来说,如果你想在 C 的内存中创建一个结构,你应该用适当的大小调用 malloc,初始化新内存的值,然后 return 将指向它的指针返回给呼叫者。这可以在您的案例中实现为:

list *create_list(list_types type)
{
    list res = malloc(sizeof(list));
    _list obj=_create_list(type); 
    res._assets=malloc(sizeof(_list)); 
    *((_list *)res._assets)=obj;
    return res; 
}

或者,如果您按照我上面的建议更改 list 的布局,只需:

list *create_list(list_types type)
{
    list res = malloc(sizeof(list); 
    res->type = type; 
    res->capacity = 1; 
    // ...
    return res; 
}

您的代码中存在多个问题:

  • 向用户隐藏实现不需要发布的间接,您可以只声明 struct list 而无需定义。

  • create_list returns static 对象的地址,毫不奇怪释放它有未定义的行为,更不用说所有列表对象对象都是实际上是同一个对象。

  • 永远不会执行 _alloc()switch 块开头的行 int n = (self)->capacity;。将此行移到 switch 语句之前。

  • 您不应使用以 _ 开头的标识符。

  • 括号 (self) 无用且令人困惑。

这是一个简化版本:

primitive_list.h:

#ifndef PRIMITIVE_LIST_H
#define PRIMITIVE_LIST_H

typedef enum { INT, LONG_INT, DECIMAL, CHAR } list_type;

typedef struct list list;

list *list_create(list_type type);
int list_push(list *self, ...);
void list_pop(list *self);
int list_get(const list *self, int index, ...);
int list_set(list *self, int index, ...);
int list_empty(const list *self);
int list_length(const list *self);
void list_print(const char *msg, const list *self);
void list_free(list **self);
#endif

primitive_list.c:

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "primitive_list.h"

struct list {
    list_type type;
    int capacity;
    int length;
    void *arr;
};

list *list_create(list_type type) {
    list *res = malloc(sizeof(*res));
    if (res) {
        res->type = type;
        res->capacity = 0;
        res->length = 0;
        res->arr = NULL;
    } else {
        fprintf(stderr, "list_create: out of memory!\n");
    }
    return res;
}

static int list_element_size(list_type type) {
    switch (type) {
    case INT:       return sizeof(int);
    case LONG_INT:  return sizeof(long long);
    case DECIMAL:   return sizeof(double);
    case CHAR:      return sizeof(char);
    }
    return 0;
}

int list_push(list *self, ...) {
    va_list arg;

    if (self->length == self->capacity) {
        if (self->capacity == 0) {
            self->capacity = 1;
            self->arr = calloc(self->capacity, list_element_size(self->type));
            if (self->arr == NULL) {
                fprintf(stderr, "list_push: out of memory!\n");
                return 0;
            }
        } else {
            size_t size = self->capacity * list_element_size(self->type);
            void *new_arr = realloc(self->arr, size * 2);
            if (new_arr == NULL) {
                fprintf(stderr, "list_push: out of memory!\n");
                return 0;
            }
            memset((char *)new_arr + size, 0, size);
            self->arr = new_arr;
            self->capacity *= 2;
        }
    }

    va_start(arg, self);
    switch (self->type) {
    case INT:
        ((int *)self->arr)[self->length++] = va_arg(arg, int);
        break;
    case LONG_INT:
        ((long long *)self->arr)[self->length++] = va_arg(arg, long long);
        break;
    case DECIMAL:
        ((double *)self->arr)[self->length++] = va_arg(arg, double);
        break;
    case CHAR:
        ((char *)self->arr)[self->length++] =(char)va_arg(arg, int);
        break;
    }
    va_end(arg);
    return self->length;
}

void list_pop(list *self) {
    if (self->length == 0) {
        fprintf(stderr, "list_pop: list is empty!\n");
    } else {
        self->length--;
    }
}

int list_get(const list *self, int index, ...) {
    int rc = 0;
    if (index >= 0 && index < self->length) {
        va_list arg;
        va_start(arg, index);
        switch (self->type) {
        case INT:
            *va_arg(arg, int *) = ((int *)self->arr)[index];
            rc = 1;
            break;
        case LONG_INT:
            *va_arg(arg, long long *) = ((long long *)self->arr)[index];
            rc = 1;
            break;
        case DECIMAL:
            *va_arg(arg, double *) = ((double *)self->arr)[index];
            rc = 1;
            break;
        case CHAR:
            *va_arg(arg, char *) = ((char *)self->arr)[index];
            rc = 1;
            break;
        }
        va_end(arg);
    }
    return rc;
}

int list_set(list *self, int index, ...) {
    int rc = 0;
    if (index >= 0 && index < self->length) {
        va_list arg;
        va_start(arg, index);
        switch (self->type) {
        case INT:
            ((int *)self->arr)[index] = va_arg(arg, int);
            rc = 1;
            break;
        case LONG_INT:
            ((long long *)self->arr)[index] = va_arg(arg, long long);
            rc = 1;
            break;
        case DECIMAL:
            ((double *)self->arr)[index] = va_arg(arg, double);
            rc = 1;
            break;
        case CHAR:
            ((char *)self->arr)[index] = (char)va_arg(arg, int);
            rc = 1;
            break;
        }
        va_end(arg);
    }
    return rc;
}

int list_empty(const list *self) {
    return self->length == 0;
}

int list_length(const list *self) {
    return self->length;
}

void list_print(const char *msg, const list *self) {
    if (msg) {
        printf("%s: ", msg);
    }
    for (int i = 0; i < self->length; i++) {
        switch (self->type) {
        case INT:
            printf("%d ", ((int *)self->arr)[i]);
            break;
        case LONG_INT:
            printf("%lld ", ((long long *)self->arr)[i]);
            break;
        case DECIMAL:
            printf("%f ", ((double *)self->arr)[i]);
            break;
        case CHAR:
            printf("%c ", ((char *)self->arr)[i]);
            break;
        }
    }
    printf("\n");
}

void list_free(list **self) {
    if (*self) {
        free((*self)->arr);
        free(*self);
        *self = NULL;
    }
}

test.c:

#include <stdio.h>
#include <stdlib.h>
#include "primitive_list.h"

int main(int argc, char **argv) {
    //Decimal List
    list *nums = list_create(DECIMAL);
    list_push(nums, 3.14159);
    list_push(nums, 6.25);
    list_push(nums, 22.2222);
    list_push(nums, 100.0);

    //Character list
    list *chars = list_create(CHAR);
    list_push(chars, 'A');
    list_push(chars, 'w');
    list_push(chars, 'Z');
    list_push(chars, 'q');
    list_push(chars, 'P');

    // print the lists
    list_print("nums", nums);
    list_print("chars", chars);

    double x;
    char c;
    if (list_get(nums, 2, &x))
        printf("nums[2] = %f\n", x);
    if (list_get(chars, 2, &c))
        printf("chars[2] = '%c'\n", c);
    if (list_set(nums, 3, 123.45))
        printf("set nums[3] to %f\n", 123.45);
    if (list_set(chars, 3, '*'))
        printf("set chars[3] to '%c'\n", '*');

    // print the lists
    list_print("nums", nums);
    list_print("chars", chars);

    // free the lists
    list_free(&nums);
    list_free(&chars);
    return 0;
}

输出:

nums: 3.141590 6.250000 22.222200 100.000000
chars: A w Z q P
nums[2] = 22.222200
chars[2] = 'Z'
set nums[3] to 123.450000
set chars[3] to '*'
nums: 3.141590 6.250000 22.222200 123.450000
chars: A w Z * P