【问题标题】:Is there any way to pass an array of an unknown type as a parameter to a function in C?有没有办法将未知类型的数组作为参数传递给 C 中的函数?
【发布时间】:2019-06-03 15:55:56
【问题描述】:

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

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

这是编译过程的输出:

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

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

*(数组 + i) = 数据;

^

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

*(数组 + i) = 数据;

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

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

*(数组 + 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; 
}

【问题讨论】:

  • 您需要转换为 char* 才能使算术有效,但这相当于 memset
  • 但是,如果我将它转换为指向 char 的指针,它会在任何地方工作吗?该解决方案编译器是否依赖?这是最好的解决方案吗?
  • 你应该以字节为单位传递长度,否则没有意义。
  • 查看标准 C bsearch()qsort() 函数。两者都对任意类型的任意大小的数组进行操作。用它们来指导你的思考。使用void * 是其中的一部分;您将需要数组中的元素数量和每个元素的大小,以及一个或多个指向函数的指针以对数据进行操作。请注意,GCC 允许您使用 void * 执行 C 标准不允许的操作。
  • 您的示例不是很有趣,因为无论数据类型如何,都可以轻松解决零填充问题(并且可以使用标准函数memset() 完成)。我建议您修改示例以使其更有趣 - 即无法使用 memset() 解决)。

标签: c ansi ansi-c


【解决方案1】:

这里的问题有两个。首先是取消引用void 指针,另一个是用它做算术。正如您在帖子中显示的那样,编译器还会向您发出警告。

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

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

【讨论】:

  • 这解释了为什么他会收到错误,但没有回答问题:“有没有办法......?”
【解决方案2】:

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

【讨论】:

  • 在 C 中无需转换为 (void*)void * 可以从任何指针类型赋值(除了限定)
  • 指针不知道它所指向的大小,只知道它从哪里开始。编译器知道大小,但到 cide 执行时,编译器早已不复存在。
  • @rici 可以提出相同的论点,即“指针”在执行时不存在。只有一些偏移到内存中。
  • @IncreasinglyIdiotic:我同意。如果你说指针知道它是一个指针,我也会发表同样的评论。事实上,指针甚至不知道它是内存中的偏移量(除了在诸如早已失效的 LISP 机器之类的架构上),就像 double 不知道它是否是 represents pounds or newton-seconds
【解决方案3】:

如果您希望用数据填充类型数组,比如说一个值为 2.2 的 double 数组,甚至是结构 { int a; 的 en 数组。任何东西 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);
}

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

【讨论】:

    【解决方案4】:

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

    这就是我要使用的版本。

    【讨论】:

    • 哦,谢谢!我越来越好,实际上这是一个有趣的事情,我想了解更多关于它的信息,我在哪里可以找到更多信息?你推荐什么书?
    • @AlbertWilliams :更多关于具体内容的信息。 IMO 关于 C 的好书在不再有用时就停止编写了——它是一种小型语言,超出了某个点,您对此无话可说。自 ANSI C89 / ISO C90 首次标准化以来对该语言的次要添加不需要太多解释,通过在相对较低的层次上理解计算机体系结构的基础知识有助于理解 C,因为它暴露了该体系结构,因此更一般的阅读计算基础可能是有序的。
    • 如果我不得不调用它,尽管没有更新到后来的 ISO C 标准,我仍然推荐开创性的 K&R C 编程语言。它将教您 C 的基础知识,但不一定教您如何在更广泛的意义上编程或构建软件。
    【解决方案5】:

    太喜欢@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;
    } 
    

    【讨论】:

      猜你喜欢
      • 2021-03-23
      • 1970-01-01
      • 2022-09-27
      • 2019-06-28
      • 1970-01-01
      • 2020-06-24
      • 2010-11-11
      • 2022-01-03
      相关资源
      最近更新 更多