我在我刚刚开始的一个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,但我无法弄清楚在哪里(或为什么)。
我将不胜感激任何人可以提供的帮助!
realloc 不一定返回相同的地址。如果它返回一个新地址,则它仅在您的函数 _arr_add 中可用。您不会返回这个新地址,因此不会更新主函数中的数组指针。
您可以将函数 _arr_add 更改为 _arr_add(void** arr, ...),这将允许您更新传递的 arr*
您可以返回新的 arr* 并在 main 中使用它,例如
arr2 = arr_add(arr2, i)
或
您可以通过使用结构体来跟踪计数、容量和数据指针来添加抽象级别
当
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;
}