在C语言中,有没有什么方法可以将未知类型的数组作为参数传递给函数?

oo7oh9g9  于 2023-04-05  发布在  其他
关注(0)|答案(5)|浏览(100)

我一直在努力提高我的C语言技能和知识。今天我试图创建一个函数,它接受任何类型的数组,但我还没有找到一个成功的方法,我使用的是ANSI C,我试图将其作为空指针传递,但当我试图通过内存操作参数时,编译器会抱怨。有什么方法可以实现它吗?我在想可能可以通过预处理器指令来完成,但我不确定。
P.S:我的目标不是用数据填充数组,这只是一个函数,而是理解和学习如何在不知道数据类型的情况下传递数据,或者允许我的函数处理不止一种类型的数据。
这是编译过程的输出:
array_test.c:在函数“array_fill”中:
array_test.c:34:13:警告:算术中使用了类型为“void *”的指针[-Wpointer-arith]

  • (array + i)= data;

^
array_test.c:34:5:警告:取消引用“void *”指针

  • (array + i)= data;

^~~~~~~~~~~~
array_test.c:34:5:错误:无效使用void表达式

  • (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; 
}
dwbf0jvd

dwbf0jvd1#

指针指向内存中某个对象的开始。* 大多数 * 指针也通过类型知道该对象在内存中的大小,例外是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,一次复制一个字节。

vs91vp4v

vs91vp4v2#

如果没有类型,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) ) ;

现在,如果array的类型是uint8_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; 
}

这是我的版本。

llmtgqce

llmtgqce3#

这里的问题是双重的。首先是解引用void指针,另一个是用它做算术。编译器也会警告你,正如你在文章中所展示的。
你不能直接向void指针添加地址,因为编译器在添加地址时不知道它需要走多远,它需要被指向的东西的大小才能做到这一点。因此,你需要在向它添加东西之前将void类型转换为具体的东西。
同样,你不能取消引用一个空指针,因为编译器不知道要提取多少字节,因为void没有任何隐式长度。

n3schb8v

n3schb8v4#

太像@越来越白痴的好答案了。
所以我会做这个维基。作为参考有用。
在C语言中,有没有什么方法可以将未知类型的数组作为参数传递给函数?
是的,代码可以用数组调用这样的函数,但是数组将被转换为数组的第一个元素的地址。这是函数将使用的地址。

some_type a[N];
foo(a);

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

int foo(void *address_of_first_element);

不幸的是,foo()丢失了类型。
有什么办法可以实现吗?
在OP的例子中,array_fill()只需要类型的大小,而不需要类型本身。所以传入类型的大小。
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;
}
0lvr5msh

0lvr5msh5#

如果你希望用数据填充一个类型的数组,比如一个值为2.2的double数组,或者甚至是一个结构为{ 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);
}

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

相关问题