c void


原文链接: c void

C语言中void和NULL - ascend的专栏 - CSDN博客
#define NULL ((void *)0)

  1. void的作用
    a. 对函数参数的限定:当不需要传入参数时,即 function (void);
    b. 对函数返回值的限定:当函数没有返回值时,即 void function(void);
  2. void指针的作用
    a. void指针可以指向任意的数据类型,即任意类型的指针可以赋值给void指针int *a;
    void *p;
    p=a;
    如果void指针赋值给其他类型,则需要强制转换;a=(int *)p;
    b. 在ANSI C标准中不允许对void指针进行算术运算,因为没有特定的数据类型,即在内存中不知道移动多少个字节;而在GNU标准中,认为void指针和char指针等同。
  3. 应用
    1. a.void指针一般用于应用的底层,比如malloc函数的返回类型是void指针,需要再强制转换;
    2. b.文件句柄HANDLE也是void指针类型,这也是句柄和指针的区别;
    3. c.内存操作函数的原型也需要void指针限定传入参数:void * memcpy (void *dest, const void *src, size_t len);
      void * memset (void *buffer, int c, size_t num );d. 面向对象函数中底层对基类的抽象。

通用指针类型void*

int *pInt   = NULL;  
char    *pChar  = NULL;  
float   *pFloat = NULL;  
struct _p_ch{  
    void *pVoid;  
    char ch;  
}*p_ch;  
          
p_ch        = NULL;  
p_ch->pVoid  = NULL;  
          
//void * can point any type of a pointer  
p_ch->pVoid  = pInt;  
p_ch->pVoid  = pChar;  
p_ch->pVoid  = pFloat;  
p_ch->pVoid  = p_ch;  
          
//void * can be pointed by any type of a pointer  
pInt    = p_ch->pVoid;  
pChar   = p_ch->pVoid;  
pFloat  = p_ch->pVoid;  
p_ch    = p_ch->pVoid;  

void*能够被任何类型(列举部分)的指针赋值,其它任何类型的指针也能被void *赋值。

void 有什么好讲的呢?如果你认为没有,那就没有;但如果你认为有,那就真的有。有点像“色即是空,空即是色”。
一、void a?
void 的字面意思是“空类型”,void *则为“空类型指针”,void *可以指向任何类型的数据。

void 几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void 变量,看看下面的例子:
void a;
Visual C++6.0 上,这行语句编译时会出错,提示“illegal use of type 'void'”。不过,即使void a 的编译不会出错,它也没有任何实际意义。
void 真正发挥的作用在于:
对函数返回的限定;
对函数参数的限定。

众所周知,如果指针p1 和p2 的类型相同,那么我们可以直接在p1 和p2 间互相赋值;如果p1 和p2 指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。例如:
float *p1;
int *p2;
p1 = p2;
其中p1 = p2 语句会编译出错,提示“'=' : cannot convert from 'int *' to 'float *'”,必须改为:
p1 = (float *)p2;
而void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换:
void *p1;
int *p2;
p1 = p2;
但这并不意味着,void *也可以无需强制类型转换地赋给其它类型的指针。因为“空类型”可以包容“有类型”,而“有类型”则不能包容“空类型”。比如,我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”。下面的语句编译出错:
void *p1;
int *p2;
p2 = p1;

提示“'=' : cannot convert from 'void *' to 'int *'”。
二、void 修饰函数返回值和参数
1、如果函数没有返回值,那么应声明为void 类型。在C 语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void 类型。例如:
add ( int a, int b )
{
return a + b;
}

intmain(int argc, char* argv[]) //甚至很多人以为main 函数无返回值 //或是为void 型的
{
printf ( "2 + 3 = %d", add ( 2, 3) );
}
程序运行的结果为输出: 2 + 3 = 5。这说明不加返回值说明的函数的确为int 函数。

因此,为了避免混乱,我们在编写C 程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为void 类型。这既是程序良好可读性的需要,也是编程规范性的要求。另外,加上void 类型声明后,也可以发挥代码的“自注释”作用。所谓的代码的“自注释”即代码能自己注释自己。

2、如果函数无参数,那么应声明其参数为void。在C++语言中声明一个这样的函数:
int function(void)
{
return 1;
}
则进行下面的调用是不合法的:function(2);

因为在C++中,函数参数为void 的意思是这个函数不接受任何参数。但是在Turbo C 2.0 中编译:
#include "stdio.h"
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}
编译正确且输出1,这说明,在C 语言中,可以给无参数的函数传送任意类型的参数,但是在C++编译器中编译同样的代码则会出错。在C++中,不能向无参数的函数传送任何参数,出错提示“'fun' : function does not take 1 parameters”。所以,无论在C 还是C++中,若函数不接受任何参数,一定要指明参数为void。
三、void 指针
1、千万小心又小心使用void 指针类型。按照ANSI(American National Standards Institute)标准,不能对void 指针进行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
ANSI 标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。也就是说必须知道内存目的地址的确切值。例如:
int *pint;
pint++; //ANSI:正确
但是大名鼎鼎的GNU(GNU's Not Unix 的递归缩写)则不这么认定,它指定void *的算法操作与char *一致。因此下列语句在GNU 编译器中皆正确:
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确
在实际的程序设计中,为符合ANSI 标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:
void * pvoid;
(char *)pvoid++; //ANSI:正确;GNU:正确
(char *)pvoid += 1; //ANSI:错误;GNU:正确

GNU 和ANSI 还有一些区别,总体而言,GNU 较ANSI 更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地符合ANSI 标准。

2、如果函数的参数可以是任意类型指针,那么应声明其参数为void *。
典型的如内存操作函数memcpy 和memset 的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
这样,任何类型的指针都可以传入memcpy 和memset 中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。如果memcpy和memset 的参数类型不是void *,而是char *,那才叫真的奇怪了!这样的memcpy 和memset明显不是一个“纯粹的,脱离低级趣味的”函数!

下面的代码执行正确:
例子1:memset 接受任意类型指针
int IntArray_a[100];
memset (IntArray_a, 0, 100*sizeof(int) ); //将IntArray_a 清0

例子2:memcpy 接受任意类型指针
int destIntArray_a[100], srcintarray_a[100]; //将srcintarray_a 拷贝给destIntArray_a
memcpy (destIntArray_a, srcintarray_a, 100*sizeof(int) );
有趣的是,memcpy 和memset 函数返回的也是void *类型,标准库函数的编写者都不是一般人。
四、void 不能代表一个真实的变量
void 不能代表一个真实的变量。因为定义变量时必须分配内存空间,定义void 类型变量,编译器到底分配多大的内存呢。

下面代码都企图让void 代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误
void 体现了一种抽象,这个世界上的变量都是“有类型”的,譬如一个人不是男人就是女人(人妖不算)。

void 的出现只是为了一种抽象的需要,如果你正确地理解了面向对象中“抽象基类”的概念,也很容易理解void 数据类型。正如不能给抽象基类定义一个实例,我们也不能定义一个void(让我们类比的称void 为“抽象数据类型”)变量。

[2] void *
附录A6.8:Any pointer to an object may be converted to type void * without loss of information. If the result is converted back to the original pointer type, the original pointer is recovered. Unlike the pointer-to pointer conversions discussed, which generally require an explicit cast, pointers may be assigned to and from pointers of type void *, and may be compared with them.
This interpretation of void * pointers is new;previously, char * pointers played the role of generic pointer. The ANSI standard specifically blesses the meeting of void * pointers with object pointers in assignments and relationals, while requiring explicit casts for other pointer mixtures.

<2> 《C语言程序设计》
[1] void
附录A6.7:一个void对象的(不存在的)值不可以以任何方式使用,也不能被显示或隐式地转换为一非空类型。因为一个空表达式表示一个不存在的值,这样的表达式只可使用在不需要值的地方。例如作为一个表达式语句或作为逗号运算符的左运算分量。

可以通过强制类型转换将表达式转换为void类型。例如,在表达式语句中一个空的强制类型转换将丢掉函数调用的任何值。

[2] void *
附录A6.8:指向任何对象的指针可以被转换为void *类型二不会丢失信息。如果将结果再转换为初始指针类型,那么初始指针被恢复。与一般需要显示的强制类型转换的指针到指针的转换不同,指针可以被赋值为void *类型指针,也可以赋值给void *类型指针,并和void *类型指针比较。

注释:对void *指针的解释是新增加的,以前char *指针扮演通用指针的角色。ANSI标准特别允许void *类型指针和其他对象指针在赋值和关系表达式中混用,而对其它的指针的混合使用则要求有显示的类型转换。

<3> 《C语言深度剖析》
[1] void
1.10.4:void不能代表一个真实的变量。因为定义变量时必须分配内存空间,定义void类型变量,编译器到底分配多大的内存呢。

[2] void *
1.10.3:按照ANSI标准,不能对void指针进行算法(如++, +=)操作。ANSI标准之所以这样认为,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。也就是说必须知道内存目的地址的确切值。如果函数的参数可以是任意类型指针,那么应声明其参数为void *。如内存操作函数的原型,void *memcpy(void *dest, const void *src, size_tlen); void *memset(void *buffer, intc, size_t num )。

(2) void和void*的应用
在Debian GNU/Linux Desktop下编程。

<1> void 和void*的使用规则
接和各经典著作中对两者的描述,在linux下编程实践。

[1] void变量

void  i;     //error: variable or field ‘i’ declared void  

当定义一个void变量时,编译程序会有以上的错误提示。

2转换

char ch = 'a';  
(void)0;  
(void)ch;  
int  i  = ((void)0, 2);  
i   = (2, (void)0);     //error: void value not ignored as it ought to be  
printf("%c\n", (void)ch);   //error: invalid use of void expression  

“(void)表达式”表示将“表达式”强制转换为void类型,整个表达式为void类型,不可以任何为值的方式被引用。

2~3. (void)0和(void)ch表示将0和ch转换为void类型得到一种void对象。

5~6. void对象不可以被引用,否则linux下会有报错。

3 (void)0

i   = (int)( (void)0 );  // error: invalid use of void expression  

void对象不可以显示或隐式的转换为其它类型。

[4] 通用指针类型void*

int *pInt   = NULL;  
char    *pChar  = NULL;  
float   *pFloat = NULL;  
struct _p_ch{  
    void *pVoid;  
    char ch;  
}*p_ch;  
          
p_ch        = NULL;  
p_ch->pVoid  = NULL;  
          
//void * can point any type of a pointer  
p_ch->pVoid  = pInt;  
p_ch->pVoid  = pChar;  
p_ch->pVoid  = pFloat;  
p_ch->pVoid  = p_ch;  
          
//void * can be pointed by any type of a pointer  
pInt    = p_ch->pVoid;  
pChar   = p_ch->pVoid;  
pFloat  = p_ch->pVoid;  
p_ch    = p_ch->pVoid;  

void*能够被任何类型(列举部分)的指针赋值,其它任何类型的指针也能被void *赋值。

5 (void *)

int i       = 1;  
int *pInt   = NULL;  
pInt    = &i;  
printf("%d\n", *( (int*)((void*)pInt) ) ); 

指向任何对象的指针可以被转换为void *类型二不会丢失信息(但仍然不可引用一个void类型的值)。如果将结果再转换为初始指针类型,那么初始指针被恢复。

[6] ANSI与GNU

int i       = 1;  
int *pInt   = NULL;  
void *pVoid = NULL;  
pInt    = &i;  
pVoid   = pInt;  
pVoid++;  
pVoid--;  
printf("%p, %p\n", pVoid, pInt );  

在GNU/Linux下,void *类型指针允许运算。某次运行,以上程序在Debian GNU/Linux下输入结果:0xbf819e7c, 0xbf819e7c。

<2> void和void*的应用场合
[1] void限定函数返回值和参数类型

add()  
{  
    return 2.1f + 3.0f;  
}  

C中,无限定函数返回类型的情况下,函数默认返回整数类型。这样的函数在linux下调用会有一条警告:warning: data definition has no type or storageclass。在main函数中调用此函数并用printf()语句输出时得到结果5.当为此函数加了void限定符后,函数返回void类型值,再在程序中应用void时编译器就会报错。

void add()  
{  
    float f = 2.1f + 3.0f;  
}  
  
int  main(void)  
{  
    add(6);  
    Renturn 0;  
}  

当函数没有任何参数时,不往函数参数列表类加”void”关键字时,像MDK-Keil就会警告,而Linux则不会。这个时候调用函数时,在GNU/Linux下再给函数参数时,程序正常运行。

void add(void)  
{  
    float f = 2.1f + 3.0f;  
}  
  
int main(void)  
{  
    add(6);  
    return 0;  
}  

当函数参数无参数时加了void,调用函数再传递参数时编译器就会报错: error: too many arguments to function ‘add’。

20限定表达式被引用
MDK-ARM中审核参数的断言为,
```c
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)FILE, LINE))

/* Exported functions ------------------------------------------------------- /
void assert_failed(uint8_t
file, uint32_t line);
(void)0的作用相当于函数返回类型为void限定,它表示此宏不可被引用。

[3] void *作为函数参数
由于void *类型指针可以指向任何类型的指针(可以指向任何对象),如果函数的参数可以是任意类型指针,就可以将其声明为void *。

2 NULL
(1) 《C陷阱与缺陷》
NULL表示内存位置0,NULL指针并不指向任何对象。因此除非是用于赋值或比较运算,出于其他任何目的使用NULL指针都是非法的。

引用NULL内存内容依编译器的不同而不同。某些C语言实现堆内存位置0强加了硬件级的读保护,在其上工作的程序如果错误使用了NULL指针,将立即终止执行。其他一些C语言实现堆内存位置0只允许读,不允许写。在这种情况下,一个NULL指针似乎指向的是某个字符串,但其内容通常不过是一堆“垃圾信息”。还有些C语言实现对内存位置0既允许读也允许写。在这种实现上面工作的程序如果错误使用了一个NULL指针,则很可能覆盖了操作系统的部分内容,造成彻底的灾难!

(2) 《C和指针》
标准定义了NULL指针,它作为特殊的指针变量,表示不指向任何东西。之所以选择0这个值是因为一种源代码约定,就机器而言,NULL指针的实际值可能与此不同,在这种情况下,编译器将负责0值和内部值之间的翻译转换。NULL指针十分有用,因为他给了程序员一种方法,表示某个特定的指针目前并未指向任何东西。

`