如何在 c 中使用 void * 制作通用函数?

Posted

技术标签:

【中文标题】如何在 c 中使用 void * 制作通用函数?【英文标题】:How to make generic function using void * in c? 【发布时间】:2012-11-08 07:04:11 【问题描述】:

我有一个 incr 函数来增加值 1 我想让它通用,因为我不想为相同的功能制作不同的功能。

假设我想将int,float,char 增加1

void incr(void *vp)

        (*vp)++;

但我知道的问题是Dereferencing a void pointer is undefined behaviour。有时可能会报错:Invalid use of void expression

我的main 功能是:

int main()


int i=5;
float f=5.6f;
char c='a';

incr(&i);
incr(&f);
incr(&c);

return 0;

问题是如何解决这个问题?有没有办法在Conly 解决它

我必须为每种数据类型定义incr() 吗?如果是,那void *有什么用

swap()sort() 存在同样的问题。我想用相同的功能交换和排序各种数据类型。

【问题讨论】:

C++ 是否有整数类型的后缀增量?为什么要重新定义它? 在 C11 中你可以使用 _Generic: robertgamble.net/2012/01/c11-generic-selections.html @Yola:这只是一个演示......实际上我必须在我的项目中创建很多可能的函数,即使在结构上也应该适用于可能的数据类型,而且我不想创建那么多函数 你考虑过使用 typeof 【参考方案1】:

您可以将第一个实现为宏:

#define incr(x) (++(x))

当然,如果您不小心,这可能会产生令人不快的副作用。这是关于 C 提供的唯一一种方法,用于将相同的操作应用于各种类型中的任何一种。特别是,由于宏是使用文本替换实现的,所以当编译器看到它时,您只有文字代码++whatever;,它可以将++ 正确应用于您提供的项目类型。使用指向 void 的指针,您对实际类型知之甚少(如果有的话),因此您无法对该数据进行太多直接操作)。

void * 通常在所讨论的函数不需要知道所涉及数据的确切类型时使用。在某些情况下(例如,qsort)它使用回调函数来避免必须知道数据的任何细节。

由于它同时进行排序和交换,让我们更详细地看一下 qsort。它的签名是:

void qsort(void *base, size_t nmemb, size_t size,
           int(*cmp)(void const *, void const *));

所以,第一个是您询问的void *——一个指向要排序的数据的指针。第二个告诉 qsort 数组中元素的数量。第三,数组中每个元素的大小。最后一个是指向可以比较单个项目的函数的指针,因此qsort 不需要知道如何执行此操作。例如,在 qsort 中的某个地方会有一些类似的代码:

// if (base[j] < base[i]) ...
if (cmp((char *)base+i, (char *)base+j) == -1)

同样,要交换两个项目,它通常会有一个本地数组用于临时存储。然后它将字节从array[i]复制到它的temp,然后从array[j]复制到array[i],最后从temp复制到array[j]

char temp[size];

memcpy(temp, (char *)base+i, size);              // temp = base[i]
memcpy((char *)base+i, (char *)base+j, size);    // base[i] = base[j]
memcpy((char *)base+j, temp, size);              // base[j] = temp

【讨论】:

谢谢,我出去了一个小时,对上面的解释有点怀疑。据此,我想每次我都会将 void 指针类型转换为 char 类型并根据它们的大小执行操作,对吧? 这些函数memset,memmove,memcpy都以void*为参数,这意味着它们与转换为char*的作用相同 @Omkant:不。当你想做指针数学时,你需要转换为char *。当/如果您不需要对指针进行数学运算时,代码通常会从其客户端接收void *,并在需要时将相同的void * 返回给客户端代码。【参考方案2】:

使用void * 不会给您带来多态行为,这正是我认为您正在寻找的。 void * 只是允许您绕过堆变量的类型检查。要实现实际的多态行为,您必须将类型信息作为另一个变量传递并在 incr 函数中检查它,然后将指针转换为所需的类型,或者通过将数据上的任何操作作为函数指针传递(其他人以qsort 为例)。 C 语言没有内置的自动多态性,因此您可以模拟它。在幕后,内置多态性的语言正在幕后做类似的事情。

详细地说,void * 是一个指向通用内存块的指针,它可以是任何东西:int、float、string 等。内存块的长度甚至不存储在指针中,让仅数据的类型。请记住,在内部,所有数据都是位和字节,而类型实际上只是逻辑数据如何物理编码的标记,因为本质上,位和字节是无类型的。在 C 中,这些信息不与变量一起存储,因此您必须自己将其提供给编译器,以便它知道是否应用操作将位序列视为 2 的补码整数、IEEE 754 双精度浮点数、ASCII 字符数据、功能等;这些都是针对不同类型数据的格式和操作的特定标准。当您将void * 转换为指向特定类型的指针时,作为程序员 断言指向的数据实际上是您将其转换为的类型。否则,您可能会出现奇怪的行为。

那么void * 有什么用处?它适用于处理不考虑类型的数据块。这对于诸如内存分配、复制、文件操作和传递指向函数的指针之类的事情是必要的。然而,在几乎所有情况下,C 程序员通过使用具有内置操作的类型来构造他们的数据,尽可能地从这种低级表示中抽象出来;或者使用结构体,对这些结构体的操作由程序员定义为函数。

您可能想check out the Wikipedia explanation 了解更多信息。

【讨论】:

【参考方案3】:

你不能完全按照你的要求去做——像增量这样的运算符需要使用特定的类型。所以,你可以做这样的事情:

enum type  
    TYPE_CHAR,
    TYPE_INT,
    TYPE_FLOAT
;

void incr(enum type t, void *vp)

    switch (t) 
        case TYPE_CHAR:
        (*(char *)vp)++;
        break;

        case TYPE_INT:
        (*(int *)vp)++;
        break;

        case TYPE_FLOAT:
        (*(float *)vp)++;
        break;
    

那么你可以这样称呼它:

int i=5;
float f=5.6f;
char c='a';

incr(TYPE_INT, &i);
incr(TYPE_FLOAT, &f);
incr(TYPE_CHAR, &c);

当然,除了定义单独的 incr_int()incr_float()incr_char() 函数之外,这并不能真正为您提供任何帮助 - 这不是 void * 的目的。

void * 的目的是在您编写的算法不关心对象的真实类型时实现的。一个很好的例子是标准的排序函数qsort(),它被声明为:

void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));

这可用于对任何类型对象的数组进行排序 - 调用者只需要提供一个可以比较两个对象的比较函数。

您的swap()sort() 函数都属于这一类。 swap() 更简单——算法不需要知道任何东西,除了对象的大小来交换它们:

void swap(void *a, void *b, size_t size)

    unsigned char *ap = a;
    unsigned char *bp = b;
    size_t i;

    for (i = 0; i < size; i++) 
        unsigned char tmp = ap[i];

        ap[i] = bp[i];
        bp[i] = tmp;
    

现在给定任何数组,您可以交换该数组中的两个项目:

int ai[];
double ad[];

swap(&ai[x], &ai[y], sizeof(int));
swap(&di[x], &di[y], sizeof(double));

【讨论】:

【参考方案4】:

使用“通用”交换的示例。

这段代码交换了两个内存块。

void memswap_arr(void* p1, void* p2, size_t size)
      
      size_t         i;
      char* pc1= (char*)p1;
      char* pc2= (char*)p2;
      char  ch;

      for (i= 0; i<size; ++i) 
        ch=     pc1[i];
        pc1[i]= pc2[i];
        pc2[i]= ch;
      

你这样称呼它:

int main() 
     int i1,i2;
     double d1,d2;
     i1= 10; i2= 20;
     d1= 1.12; d2= 2.23;
     memswap_arr(&i1,&i2,sizeof(int));     //I use memswap_arr to swap two integers
     printf("i1==%d i2==%d \n",i1,i2);     //I use the SAME function to swap two doubles
     memswap_arr(&d1,&d2,sizeof(double));      
     printf("d1==%f d2==%f \n",d1,d2);
     return 0;

我认为这应该让您了解如何将一个函数用于不同的数据类型。

【讨论】:

这对 OP 的增量要求有何帮助? 他要求:“我想用相同的函数交换和排序所有类型的数据类型。” 这就是我的回答:)我认为一个很好的例子可以做一个很好的解释。我试图提供帮助,而不是混淆:) 结合宏#define swap(x, y) swap_impl(&amp;x, &amp;y, sizeof x)和boom,不需要一直使用sizeof操作符。很好的例子!【参考方案5】:

抱歉,如果这可能无法回答广泛的问题“如何在 c 中使用 void * 制作通用函数?”.. 但您似乎遇到的问题(增加任意类型的变量,并交换 2 个未知类型的变量)使用宏比函数和指向 void 的指针更容易完成。

递增很简单:

#define increment(x) ((x)++)

为了交换,我会这样做:

#define swap(x, y)                  \
(                                  \
        typeof(x) tmp = (x);        \
        (x) = (y);                  \
        (y) = tmp;                  \
)

...根据我的测试,它适用于整数、双精度和字符指针(字符串)。

虽然递增宏应该非常安全,但交换宏依赖于 typeof() 运算符,它是 GCC/clang 扩展,不是标准 C 的一部分(如果你真的只使用 gcc 或 clang 编译,这个应该问题不大)。

我知道那种回避了原来的问题;但希望它仍然可以解决您最初的问题。

【讨论】:

【参考方案6】:

您可以使用类型通用工具(C11 标准)。如果您打算使用更高级的数学函数(比++ 运算符更高级),您可以转到&lt;tgmath.h&gt;,这是&lt;math.h&gt;&lt;complex.h&gt; 中函数的类型泛型定义。

您还可以使用_Generic 关键字将类型泛型函数定义为宏。下面是一个例子:

#include <stdio.h>

#define add1(x) _Generic((x), int: ++(x), float: ++(x), char: ++(x), default: ++(x))

int main()
  int i = 0;
  float f = 0;
  char c = 0;

  add1(i);
  add1(f);
  add1(c);

  printf("i = %d\tf = %g\tc = %d", i, f, c);

您可以在Rob's programming blog 的这篇博文中找到有关language standard 的更多信息和更复杂的示例。

至于* void,交换和排序问题,最好参考Jerry Coffin的回答。

【讨论】:

【参考方案7】:

您应该在取消引用之前将指针转换为具体类型。所以你还应该添加代码来传递指针变量的类型。

【讨论】:

以上是关于如何在 c 中使用 void * 制作通用函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 fetch 制作通用 API 调用函数

c语言 如何调用另一函数变量

如何制作适应模型的通用函数?

Keil中如何用Keil中如何用汇编调用C函数?

在 C 中为链表编写通用搜索函数?

如何为 Android Room 请求制作通用 AsyncTask