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

时间:2012-11-20 08:43:12

标签: c void-pointers

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

假设我希望int增加floatchar1

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

问题是如何解决这个问题?有没有办法在仅C

中解决它

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

的用途是什么

swap()sort()相同的问题。我希望使用相同的功能交换和排序所有类型的数据类型。

7 个答案:

答案 0 :(得分:19)

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

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

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

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]复制到其临时,然后从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

答案 1 :(得分:13)

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

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

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

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

答案 2 :(得分:4)

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

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

答案 3 :(得分:1)

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

此代码交换两块内存。

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

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

很抱歉,如果这可能不是对广泛问题的回答,那么如何在c中使用void *制作通用功能?&#34; ..但问题是你似乎有(递增一个任意类型的变量,并交换2个未知类型的变量)使用宏比使用函数和指向void的更容易。

增量很简单:

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

对于交换,我会做这样的事情:

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

...基于我的测试,它适用于int,double和char指针(字符串)。

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

我知道那种躲过了原来的问题;但希望它仍能解决你原来的问题。

答案 6 :(得分:0)

您可以使用类型通用设施(C11标准)。如果您打算使用更高级的数学函数(比++运算符更高级),您可以转到<tgmath.h><math.h>和{<complex.h>中函数的类型泛型定义1}}。

您还可以使用_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);
}

您可以在language standard的帖子中找到有关Rob's programming blog以及更多有关soffisticated示例的详细信息。

关于* void,交换和排序问题,请参阅Jerry Coffin的答案。