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

时间:2019-01-08 22:21:14

标签: c ansi ansi-c

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

PS:我的目标不是用数据填充数组,而只是一个函数,而是了解和学习如何在不知道数据类型的情况下传递数据,或者让我的函数不仅仅用于处理数据一种数据。

这是编译过程的输出:

  

array_test.c:在“ array_fill”函数中:

     

array_test.c:34:13:警告:算术[-Wpointer-arith]中使用的'void *'类型的指针

     

*(array + i)=数据;

     

^

     

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

     

*(array + i)=数据;

     

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

     

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

     

*(array + i)=数据;

     

^

这是我的代码:

#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; 
}

5 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

这里的问题有两个。首先是解引用void指针,另一个是对其进行算术运算。正如您在帖子中所显示的,编译器还会警告您。

您不能直接添加到void指针,因为编译器不知道添加地址时需要走多远,它需要指向的对象的大小,才能做吧。因此,您需要先将虚空类型转换为具体内容,然后才能向其中添加内容。

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

答案 2 :(得分:1)

没有类型,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

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) );

2D阵列

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; 
}

这就是我要使用的版本。

答案 3 :(得分:1)

太像@Increasingly Idiotic的好答案。

因此,我将制作此Wiki。有用作为参考。


  

有什么方法可以将未知类型的数组作为参数传递给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;
} 

答案 4 :(得分:0)

如果您希望用数据填充类型的数组,那么可以说是值是2.2的double数组,甚至是结构{int a;任何b};那么基本上答案是“不”,你不能这样子。

您可以为此使用宏,例如

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);
}

但这不是函数。

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

def main():
  args = docopt(__doc__, version = __version__)
  img_filter = Filter(args['<curves>'], 'crgb')
  im = Image.open(args['<filepath>'])
  image_array = np.array(im)
  filter_manager.add_filter(img_filter)
  filter_array = filter_manager.apply_filter('crgb', image_array)
  im = Image.fromarray(filter_array)
  output_name = '%s_%s.png' % (get_name(args['<filepath>']), get_name(args['<curves>']))
  im.save(output_name)

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