请稍侯

void和void*

05 February 2014

  测试环境:win7下使用VS2010 和 ubuntu 12.04.3下使用gcc编译运行程序

  void的字面意思是“无类型”,void不可以用来定义变量,这一点两个cl编译器和gcc编译器编译规则都做了限制,编译时都会给出error。仔细一想,使用void定义一个无类型的数据也没有什么实际的意义,void的作用看起来就只有限定函数的返回值和限定函数的参数了。

  void*则为“无类型指针”。void* 就是可以指向任何数据类型的指针,可以被任何类型的指针所赋值。c++ primer里面写的是任何非const 数据类型的指针都可以被赋值给void*型的指针, void*型指针被用于对象的确切类型未知或者在特定环境下对象的类型会发生变化的情况下。

  下面我们针对这两种情况讨论。

1 对象的确切类型未知

  来看这一段代码

#include "stdio.h"
int main()
{
    char *charPtr = "I am the pointer of a string\n";
    int i = 10;
    double j = 3.14;
    int *intPtr =&i;
    double *floatPtr = &j;
    void *temp;
    temp = charPtr;
    printf("%s",(char*)temp);
    temp = intPtr;
    printf("%d\n",*(int*)temp);
    temp = floatPtr;
    printf("%f\n",*(double*)temp);
    return 0;
}

  一般指针之间的相互赋值,需要数据类型一致才能赋值,如果数据类型不同需要强制类型转换来保证赋值的有效性。这段代码说明了任何类型的指针都可以直接赋值给void*,无需进行强制类型转换。运行结果如下:

1

  那么是不是void *也可以无需强制类型转换地赋给其它类型的指针呢?

#include "stdio.h"
int main()
{
    char *charPtr = "I am the pointer of a string\n";
    int *a;
    void *temp = charPtr;
    a = temp;
    printf("%s",a);
    return 0;
}

  在两个环境下测试发现:VS中 会报错

2

  而在gcc中,除了给出了printf的警告,将void*赋值给int*时都没有出现编译错误。完整的打印出了字符串,尽管a是一个int*类型的。

3

2 特定环境下对象的类型会发生变化的情况下

  这种情况也就是C语言实现泛型编程的思想,泛型编程让你编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。所谓泛型(Genericity),是指具有在多种数据类型上皆可操作的含意。C++通过参数化类型来实现通用的容器。如Java则引入了单根继承的概念。比泛型更加让你熟悉的可能就是STL,Standard template library,标准模板库。STL是一种高效、泛型、可交互操作的软件组件。STL以迭代器 (Iterators)和容器(Containers)为基础,是一种泛型算法(Generic Algorithms)库,容器的存在使这些算法有东西可以操作。STL包含各种泛型算法(algorithms)、泛型指针(iterators)、泛型容器(containers)以及函数对象(function objects)。STL并非只是一些有用组件的集合,它是描述软件组件抽象需求条件的一个正规而有条理的架构。

  而在C语言中,同样也可以通过一些手段实现这样的泛型编程,如通过宏。这里使用void*。看下面一段代码:实现的就是一个swap函数的一个简单的泛型函数。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void voidptr_swap(void *p1, void *p2, size_t size)
{
void *tmp;
tmp = malloc(size);
memcpy(tmp, p1, size);
memcpy(p1, p2, size);
memcpy(p2, tmp, size);
free(tmp);
}
int main(int argc, char *argv[])
{
    int a = 2;
    int b = 3;
    double c = 3.14;
    double d = 6.28;
    printf("Before swap a = %d,b = %d\n", a, b);
    voidptr_swap(&a, &b, sizeof(int));
    printf("After swap a = %d,b = %d\n",a,b);
    printf("Before swap c = %f,d = %f\n", c, d);
    voidptr_swap(&c, &d, sizeof(double));
    printf("After swap c = %f,d = %f\n",c,d);
    return 0;
}

  运行结果如下:

4

  最后还有一个小问题,关于void*类型大小。我们用下面一段代码来测试,一般来说temp++表示的就是本身的地址,加上sizeof(temp)。

#include "stdio.h"
int main()
{
    void *temp;
    temp++;
    temp++;
    printf("Hello\n");
    return 0;
}

  同样的代码,我在VS下报错,显示:

5

  而在gcc环境下,没有错误,调试的时候显示temp的地址每次加1,查了一下,发现原来gcc指定void*的算法操作与char*一致。 6

总结

1 类型转换

gcc编译器中:
  任何类型的指针都可以直接赋值给void*,无需进行强制类型转换。
  将void*赋值给其他指针类型时虽然会有警告,但是程序结果正确。
cl编译器(vs)中:
  任何类型的指针都可以直接赋值给void*,无需进行强制类型转换。
  将void*赋值给其他指针类型时报错。

2 使用void*可以达到实现泛型函数的目的。

3 void*大小为1个字节。