如何在 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;
问题是如何解决这个问题?有没有办法在C
only 解决它
或
我必须为每种数据类型定义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(&x, &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 标准)。如果您打算使用更高级的数学函数(比++
运算符更高级),您可以转到<tgmath.h>
,这是<math.h>
和<complex.h>
中函数的类型泛型定义。
您还可以使用_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 * 制作通用函数?的主要内容,如果未能解决你的问题,请参考以下文章