如何正确地将 void 类型转换为数组

How to properly cast to a void type as an array

我正在用 C 解决一个问题,我想创建一个动态增长的数组,如果可能的话,对不同的数据类型使用相同的函数。目前我有一个名为 Array 的结构,它使用一个名为 *array 的空数据类型,它是一个指向数组的指针。它还包含 len 存储数组的活动长度,size 存储分配内存的长度,以及 elem 存储用于动态增长的数据类型的长度数组。

另外,我正在使用三个函数。函数 initiate_array 为结构中的 array 变量分配内存并实例化除了一个结构元素之外的所有元素。函数 init_array 充当 initiate_array 的包装器,并在结构中实例化变量 elem。最后,函数 append_array 将 data/indices 添加到数组并在必要时重新分配内存。

此时Array结构,函数initiate_arrayinit_array与数据类型无关;但是,append_array 是针对 int 变量进行硬编码的。我试图通过将输入 int item 变为 void item 来使 append_array 在某种程度上独立于数据类型,但随后我在每个位置都收到一个编译时错误,代码为 ((int *)array->array)[array->len - 1] = item告诉我我不能转换为 void。

我的代码如下,有人建议我如何实现 append_array 函数以独立于 item 的数据类型吗?

注意:我还有一个在执行结束时释放内存的函数,但我从这个问题中省略了它,因为它不相关。

array.h

#ifndef ARRAY_H
#define ARRAY_H

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

typedef struct
{
    void *array;
    size_t len;
    size_t size;
    int elem;
} Array;

void initiate_array(Array *array, size_t num_indices);
Array init_array(int size, size_t num_indices);
void append_array(Array *array, int item);

#endif /* ARRAY_H */

array.c

#include "array.h"

void initiate_array(Array *array, size_t num_indices) {
    void *pointer;

    pointer = malloc(num_indices * array->elem);

    if (pointer == NULL) {
        printf("Unable to allocate memory, exiting.\n");
        free(pointer);
        exit(0);
    }
    else {
        array->array = pointer;
        array->len = 0;
        array->size = num_indices;
    }
}

Array init_array(int size, size_t num_indices) {
    Array array;
    array.elem = size;
    initiate_array(&array, num_indices);
    return array;
}

void append_array(Array *array, int item) {
    array->len++;
    if (array->len == array->size){
        array->size *= 2;
        void *pointer;
        pointer = realloc(array->array, array->size * array->elem);

        if (pointer == NULL) {
            printf("Unable to reallocate memory, exiting.\n");
            free(pointer);
            exit(0);
        }
        else {
            array->array = pointer;
            ((int *)array->array)[array->len - 1] = item;
        }
    }
    else
        ((int *)array->array)[array->len - 1] = item;
}

main.c

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

int main(int argc, char** argv)
{
    int i, j;
    size_t indices = 20;
    Array pointers = int_array(sizeof(int), indices);

    for (i = 0; i < 50; i++)
    {
        append_int_array(&pointers, i);
    }

    for (i = 0; i < pointers.len; i++)
    {
        printf("Value: %d Size:%zu \n",((int *) pointers.array)[i], pointers.len);
    }

    return (EXIT_SUCCESS);
}

类型void是一个不完整的类型,不能完成,所以你不能给它赋值或从它赋值,也不能把它用作数组参数类型。

你可以做的是更改 append_array 以将 void * 作为参数指向要添加的数据。然后将数据指针转换为 char *,这样您就可以进行单字节指针运算以获得正确的偏移量,然后使用 memcpy 复制数据。

void append_array(Array *array, void *item) {
    array->len++;
    if (array->len == array->size){
        array->size *= 2;
        void *pointer;
        pointer = realloc(array->array, array->size * array->elem);

        if (pointer == NULL) {
            printf("Unable to reallocate memory, exiting.\n");
            free(array->array);
            exit(0);
        }
        else {
            array->array = pointer;
        }
    }
    char *p = (char *)array->array + (array->len - 1) * array->elem;
    memcpy(p, item, array->elem);
}

您将无法通过传递整数文字来调用此函数来添加,但您可以使用复合文字的地址。

append_array(array, &(int){ 3 });

应该是这样的,或者你可以用typeof改进一下。 或者,使用 void **array;

#include <assert.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    void *array;
    size_t size;
    size_t capacity;
    int elem_size;
} Array;

void initiate_array(Array *array, size_t num_indices);
Array init_array(int size, size_t num_indices);
void append_array(Array *array, void *item);

void initiate_array(Array *array, size_t num_indices) {
    void *pointer;

    pointer = malloc(num_indices * array->elem_size);

    if (pointer == NULL) {
        printf("Unable to allocate memory, exiting.\n");
        // free(pointer);
        exit(0);
    } else {
        array->array = pointer;
        array->size = 0;
        array->capacity = num_indices;
    }
}

Array init_array(int elem_size, size_t num_indices) {
    Array array;
    array.elem_size = elem_size;
    initiate_array(&array, num_indices);
    return array;
}

void append_array(Array *array, void *item) {
    if (array->size == array->capacity) {
        // extend the array
    }

    memcpy(array->array + array->size * array->elem_size, item,
           array->elem_size);
    array->size++;
}

int main(void) {
    Array arr = init_array(sizeof(int), 10);

    int item = 1;
    append_array(&arr, &item);
    item = 2;
    append_array(&arr, &item);
    item = 3;
    append_array(&arr, &item);

    return 0;
}

我会先看看 append_arrayvoid 不是一个完整的类型,也就是说你不能按值传入 void 个对象。不过,您可以通过引用传递它们,并且 void * 也可以引用任何其他类型:

void append_array(Array *array, void *item) {

现在,您假设要传入单个元素。但为什么要停在那里呢?你可以让你的函数签名看起来像这样:

void append_array(Array *array, void *items, size_t count) {

这里的附加警告是 array->size * 2 可能不足以保存附加数据。您可以改用 (array->len + count) * 2

假设 array->size 足够大,您可以直接使用 memcpy 复制元素并转换为 char *,标准保证具有 size-1 元素:

memcpy((char *)array->array + array->len * array->elem, items, count * array->elem);

请注意,我在这里使用 array->len 作为索引。那是因为我的下一个建议是仅在制作副本后增加 array->len。这将使您的尺寸检查更简单,并且不会像现在这样重新分配一个元素。请记住 array->len 不仅是数组的大小,它也是您要附加到的基于零的索引。

if (array->len + count > array->size) {

对于单个元素,条件为

if (array->len >= array->size) {

最后,我强烈建议您 return 一个整数错误代码而不是退出。此功能的用户应该期望能够在内存错误的情况下至少进行清理,或者可能释放缓存元素,而不是单方面崩溃。

这是最终函数的样子:

int append_array(Array *array, void *items, size_t count)
{
    if (array->len + count > array->size) {
        size_t size = (array->len + count) * 2;
        void *pointer = realloc(array->array, size * array->elem);
        if (pointer == NULL) {
            return 0;
        }
        array->array = pointer;
        array->size = size;
    }
    memcpy((char *)array->array + array->len * array->elem, items, count * array->elem);
    array->len += count;
    return 1;
}

以这种方式编写函数有一个小缺点:由于右值没有地址,您不能调用

append_array(&array, &3, 1);

您可以通过两种方式解决此问题。

  1. 创建一个临时变量或缓冲区来保存值:

    int tmp = 3;
    append_array(&array, &tmp, 1);
    
  2. 制作一个 type-specific 可以接受完整类型元素的包装器。这是可行的,因为 C 是纯粹的 pass-by-value(即复制),所以你可以做

    int append_int(Array *array, int value)
    {
        return append_array(array, &value, 1);
    }
    

    在这种情况下,您实际上是在使用新的堆栈帧来保存第一个示例中 tmp 的值。