有没有办法将未知类型的数组作为参数传递给 C 中的函数?

Is there any way to pass an array of an unknown type as a parameter to a function in C?

我一直在努力提高我在 C 方面的技能和知识。今天我尝试创建一个接受任何类型数组的函数,但我还没有找到成功的方法,我正在使用ANSI C 和我试图将它作为空指针传递,但是当我试图遍历使用参数操作的内存时,编译器会抱怨。有什么办法可以实现吗?我在想可能可以通过预处理器指令来完成,但我不确定。

P.S:我的目标不是用数据填充数组,那只是一个函数,而是理解和学习如果我不知道它的类型或允许我的函数如何传递数据处理不止一种类型的数据。

这是编译过程的输出:

array_test.c: In function 'array_fill':

array_test.c:34:13: warning: pointer of type 'void *' used in arithmetic [-Wpointer-arith]

*(array + i) = data;

^

array_test.c:34:5: warning: dereferencing 'void *' pointer

*(array + i) = data;

^~~~~~~~~~~~

array_test.c:34:5: error: invalid use of void expression

*(array + i) = data;

^

这是我的代码:

#include <stdio.h>

#define array_length(array) (sizeof(array)/sizeof(array[0]))

#define ARBITRARY_SIZE 10    

typedef enum
{
  false,
  true
} bool;

int array_fill(void*, int, int);

int main(int argc, char* argv[])
{
  int array[ARBITRARY_SIZE];
  int i;
 
  array_fill(array, array_length(array), 0);

  for(i = 0; i < array_length(array); i++)
  {
    printf("array[%d]: %d\n", i, *(array + i));
  }

  return 0;
} 

int array_fill(void* array, int size, int data)
{
  int i;
  
  for(i = 0; i < size; i++)
  {
    *(array + i) = data; 
  }

  /*I will implement a check later, in case of errors.*/
  return 0; 
}

这里的问题有两个。第一个是取消引用 void 指针,另一个是用它做算术运算。正如您在 post.

中所示,编译器也会警告您

您不能直接添加到 void 指针,因为编译器在添加地址时不知道它需要走多远,它需要被指向的东西的大小,能够做到。因此,您需要先将 void 转换为具体的内容,然后才能向其添加内容。

同样,您不能取消引用 void 指针,因为编译器同样不知道要提取多少字节,因为 void 没有任何隐式长度。

指针指向内存中某个对象的开头。 大多数 指针也可以通过类型知道该对象在内存中的大小,void *.

例外。

例如如果指向 32 位整数的指针的值为 0,我们知道位 0 到 31 包含对应于该 32 位整数的数据。

0  31
|---| <- 32 bits storing the data for a 32-bit integer

对于你的问题更重要的是,如果我们知道这个指针指向一个32位整数序列,我们就知道我们可以通过将指针向前移动32位来得到下一个整数。例如。第二个整数将从 32 开始。

0  31 32 63
|---| |---|

This is what int[2]. might look like in memory on a 32-bit system

这就是指针算法的工作原理。使用空指针 void *array 你不能做 array++ 甚至 *array 因为没有办法知道指针前进多少位或对应于 array 多少位。

0    ??
|----

We don't know how many bits a void pointer points to

您也可以技术上通过传递对象的大小来解决这个问题,尽管这可能不是一个好主意。

// array points to the memory to be filled
// len is the number of elements in the array
// size is the size of an element (in bytes)
// fill points to an object to be used to fill array
void array_fill(void* array, int len, size_t size, void* fill) {
    // char is always a single byte
    char* byte_ptr = (char*) array;

    for (int i = 0; i < len; i++) {
        // Fill the current element
        memcpy(byte_ptr, fill, size);

        // Advance byte_ptr the correct number of bytes
        byte_ptr += size;
    }
}

如果您不想使用 memcpy,您也可以手动将 fill 对象复制到 byte_ptr,一次一个字节。

如果您希望用数据填充一个类型的数组,比如说一个值为 2.2 的双精度数组,或者甚至是一个结构数组 { int a;任何东西 b };那么基本上答案是否定的,你不能这样做。

你可以为此使用宏,比如

# define FILL_ARRAY(arr, data, len) for (size_t i = 0; i < len; i++) { arr[i] = data }

但它不是函数。

但是您可以创建一个函数,该函数接受能够分配数据的回调,例如:

void fill_array(void * array, size_t item_size, size_t array_len, void (*cb)(void *))
{
    unsigned char *bytes = array;
    for (size_t i = 0; i < array_len; i++) {
        cb(&bytes[i * item_size]);
    }
}

void fill_double(void *data)
{
    const value = 2.2;
    double *ptr = *data;

    *data = value;
}

int main(void)
{
    double array[30];

    fill_array(array, sizeof double, 30, fill_double);
}

不确定这是否值得,但它看起来应该是您问题的解决方案(未编译,可能包含错误)

没有类型,array 引用的数据没有元素大小,因此指针算法未定义。要使表达式有效,您必须将 array 转换为适当的数据类型,例如:

*((int*)array + i) = data; 

但这违背了拥有未定义类型的目的。简单且最有效的解决方案是为您希望填充的每种类型的数组定义单独的函数。可以定义一个函数来处理多个 integer 类型,因此:

int array_fill(void* array, size_t array_length, long long data, size_t data_size )
{
    if( data_size > sizeof(data) )
    {
        data_size = sizeof(data) ;
    }

    for( size_t i = 0; i < array_length; i++)
    {
        for( int b = 0; b < data_size; b++ )
        {  
            ((char*)array)[i * data_size + b] = (data >> (b * 8)) & 0xff ;
        }
    }

  return 0; 
}

以上做了两个假设:

  • 目标使用的是小端字节序,
  • 目标具有 8 位 char 类型。

如果这些假设不正确,则需要进行修改。请注意,我还使用了数组索引符号而不是指针算术 - 它导致括号更少,因此更易于阅读。

然后可能会调用该函数,例如在您的情况下:

array_fill( array, array_length(array), 0, sizeof(*array) ) ;

array可以有任何类型。

然而,用零填充数组是一种特殊情况,不需要这种复杂性(即,对于您的示例用法,它没有任何用处)。以下:

memset( array, sizeof(array), 0 ) ;

具有相同的效果,在任何情况下,整数 0 的所有字节都为零。该函数更适用于每个字节不同的值。

array_fill( array, array_length(array), 0x01234ABCD, sizeof(*array) ) ;

现在,如果 arrayuint8_t 类型,它将填充 0xCD,如果是 uint16_t,则填充 0xABCD。如果它是 long long 并且在 64 位类型的目标上,它将填充 0x0000000001234ABCD.

如果有点麻烦,也可以使用此函数来填充 floatdouble 数组,例如:

double array[ARBITRARY_SIZE];
double x = 0.5 ;
array_fill(array, ARBITRARY_SIZE, *(long long*)(&x), sizeof(array) );

另一种允许聚合类型甚至任意长度序列用作填充的方法是:

int array_fill( void* array, size_t array_length, 
                const void* fill_pattern, size_t fill_pattern_length )
{
    for( size_t i = 0; i < array_length; i++)
    {
        for( int b = 0; b < fill_pattern_length; b++ ) 
        {  
            ((char*)array)[i * fill_pattern_length + b] = ((char*)fill_pattern)[b] ;
        }
    }

  return 0; 
}

那么它可以真正用于任何类型。示例:

双人

double array[ARBITRARY_SIZE], x = 0.5 ;
array_fill( array, ARBITRARY_SIZE, &x, sizeof(x) );

int

int array[ARBITRARY_SIZE], x = 123456 ;
array_fill( array, ARBITRARY_SIZE, &x, sizeof(x) );

结构

struct S{ int x; double f ; } array[ARBITRARY_SIZE], x = {1234, 0.5};
array_fill( array, ARBITRARY_SIZE, &x, sizeof(x) );

二维数组

int array[ARBITRARY_SIZE][2], x[2] = { 12, 98 } ;
array_fill( array, ARBITRARY_SIZE, &x, sizeof(x) );

该实现避免了端序问题,但不能接受文字常量初始化程序,因为您无法获取地址。

最后一个实现可以改进(简化并提高效率);例如:

int array_fill( void* array, size_t array_length, 
                const void* fill_pattern, size_t fill_pattern_length )
{
    for( size_t i = 0, byte_index = 0; 
         i < array_length; 
         i++, byte_index += fill_pattern_length )
    {
        memcpy( &((char*)array)[byte_index], fill_pattern, fill_pattern_length ) ;
    }

  return 0; 
}

这就是我要使用的版本。

太像了好答案。

所以我会制作这个 wiki。有参考价值。


Is there any way to pass an array of an unknown type as a parameter to a function in C?

是的,代码可以用数组调用这样的函数,但数组将被转换为数组第一个元素的地址。这是函数将使用的地址。

some_type a[N];
foo(a);

要使函数接受任何数组对象类型,函数参数为void *

int foo(void *address_of_first_element);

很遗憾 foo() 丢失了类型。


Is there any way to achieve it?

在 OP 的情况下,array_fill() 只需要类型的大小而不是类型本身。所以传入类型的size。

OP 发现需要数组大小并传递它 - 很好。还需要的是元素的大小和指向填充值的指针

要进行指针数学运算,请将 void* 转换为 char *,因为 void* 上的指针数学运算未由 C 定义。

// int array_fill(void* array, int size, int data)
int array_fill(void* array, size_t a_size, const char *fill, size_t e_size) {
  char *data = array;
  for(size_t a = 0; a < a_size; a++) {
    memcpy(data, fill, e_size);  // Copy `e_size` bytes.
    data += e_size;              // Advance `e_size` bytes. 
  }
  return 0; 
}

int main(void) {
  int array[ARBITRARY_SIZE], fill_value = 42;    
  array_fill(array, array_length(array), &fill_value, sizeof array[0]);

  for(size_t i = 0; i < array_length(array); i++) {
    printf("array[%zu]: %d\n", i, *(array + i));
  }

  return 0;
}