C 中的自定义动态数组结构存在内存问题

问题描述 投票:0回答:2

我在我刚刚开始的一个C学习项目中基于这篇文章创建了一个动态数组结构。 我对 create 和 add 函数的实现与本文中的实现几乎相同,只有一些细微的更改(我认为不会破坏任何内容)。例如,我有第三个“标题”字段用于数据类型的步幅(因为我希望能够使用不同的数据类型)。

以下是实现(结合 arr.h 和 arr.c):

// arr.h
typedef enum {
    LENGTH,
    STRIDE,
    CAPACITY,
    NUM_PROPERTIES
} Header;

#define arr_create(type) _arr_create(sizeof(type))
#define arr_add(arr, num) _arr_add(arr, &num)
#define arr_delete(arr) free(((size_t *)arr) - NUM_PROPERTIES)   

// arr.c
void *_arr_create(size_t stride) {
    size_t *arr = malloc(sizeof(size_t) * NUM_PROPERTIES + stride);
    if (arr == NULL) {
        fprintf(stderr, "Error allocating memory in _arr_create()\n");
        exit(1);
    }

    arr[LENGTH] = 0; /* size */
    arr[STRIDE] = stride; /* stride */
    arr[CAPACITY] = 1;
    
    return (void *)(arr + NUM_PROPERTIES);
}

void _arr_add(void *arr, void *num) {
    size_t *raw = ((size_t *) arr) - NUM_PROPERTIES;
    printf("LENGTH: %d\n", raw[LENGTH]);
    if (raw[LENGTH] == raw[CAPACITY]) {
        raw[CAPACITY] = raw[CAPACITY] * 2;
        raw = realloc(raw, sizeof(size_t) * NUM_PROPERTIES + raw[CAPACITY] * raw[STRIDE]);
        if (raw == NULL) {
            fprintf(stderr, "Error reallocating memory in _arr_add()\n");
            exit(1);
        }
    }
    memcpy(arr + raw[LENGTH] * raw[STRIDE], num, raw[STRIDE]);
    raw[LENGTH] = raw[LENGTH] + 1;
}

我的问题是,在 main.c 中,我无法一次循环遍历多个数组。例如,这工作得很好:

int *arr = arr_create(int);
for (int i=0; i<10; i++)
    arr_add(arr, i);

但这不起作用:

int *arr1 = arr_create(int);
int *arr2 = arr_create(int);
for (int i=0; i<10; i++) {
    arr_add(arr1, i);
    arr_add(arr2, i);   
}

输出(来自 _arr_add() 中的 printf)是这样的:

LENGTH: 0
LENGTH: 0
LENGTH: 1
LENGTH: 1
LENGTH: 2
LENGTH: 2
LENGTH: 3
LENGTH: 1544099056

一旦读取到此长度,它就会挂起几秒钟(可能是因为该长度在 memcpy 中使用),然后失败且没有错误消息。

如果我创建一个数组,循环并添加值,那么在该循环完成后,我创建另一个数组并循环它,没有任何失败。肯定与内存中的其他东西有一些重叠,所以我可能在某个地方错误地使用了 malloc 或 realloc,但我无法弄清楚在哪里(或为什么)。

我将不胜感激任何人可以提供的帮助!

c memory-management
2个回答
0
投票

realloc 不一定返回相同的地址。如果它返回一个新地址,则它仅在您的函数 _arr_add 中可用。您不会返回这个新地址,因此不会更新主函数中的数组指针。

  1. 您可以将函数 _arr_add 更改为 _arr_add(void** arr, ...),这将允许您更新传递的 arr*

  2. 您可以返回新的 arr* 并在 main 中使用它,例如

    arr2 = arr_add(arr2, i)

  3. 您可以通过使用结构体来跟踪计数、容量和数据指针来添加抽象级别


0
投票

realloc
成功时,前一个指针将失效,基于此值计算的任何指针也将失效。

给定

size_t *raw = ((size_t *) arr) - NUM_PROPERTIES;

然后

raw = realloc(raw, sizeof(size_t) * NUM_PROPERTIES + raw[CAPACITY] * raw[STRIDE]);

使

arr
无效,稍后由

使用
memcpy(arr + raw[LENGTH] * raw[STRIDE], num, raw[STRIDE]);

并且还会使

arr1
arr2
中的原始指针值无效。

-fsanitize=address
尝试写入陈旧内存时,使用 memcpy 编译此代码会导致
heap-use-after-free
违规。

在您链接的文章中,宏包含对每次重新分配后计算的新指针值的局部变量的重新分配:

(arr) = (void *) &raw[2];\

宏魔法使这项工作以一种产生“干净”语法的方式工作。


请注意

memcpy(arr + raw[LENGTH] * raw[STRIDE], num, raw[STRIDE]);

void *
上执行指针算术,这是编译器扩展。例如,GNU 将
sizeof (void)
定义为
1
。可移植 C 应该首先将此指针转换为指向字符类型的指针(即
unsigned char *
)。

此外,在

printf("LENGTH: %d\n", raw[LENGTH]);
中,对于
%zu
类型的值,说明符应为
size_t


一个非常快速的解决方法是实现类似的宏魔法:

#define arr_add(arr, num) \
    do { \
        arr = _arr_add(arr, &num); \
    } while (0)

并更改

_arr_add
的定义以返回地址:

void *_arr_add(void *arr, void *num) {
    size_t *raw = ((size_t *) arr) - NUM_PROPERTIES;
    printf("LENGTH: %zu\n", raw[LENGTH]);
    if (raw[LENGTH] == raw[CAPACITY]) {
        raw[CAPACITY] = raw[CAPACITY] * 2;
        raw = realloc(raw, sizeof(size_t) * NUM_PROPERTIES + raw[CAPACITY] * raw[STRIDE]);

        if (raw == NULL) {
            fprintf(stderr, "Error reallocating memory in _arr_add()\n");
            exit(1);
        }
    }
    memcpy((unsigned char *) (raw + NUM_PROPERTIES) + raw[LENGTH] * raw[STRIDE], num, raw[STRIDE]);
    raw[LENGTH] = raw[LENGTH] + 1;
    return raw + NUM_PROPERTIES;
}
© www.soinside.com 2019 - 2024. All rights reserved.