C语言中void和NULL

来源:互联网 发布:数据集中存储的好处 编辑:程序博客网 时间:2024/06/05 15:22

1 void 和void*

(1) 介绍

<1> 《The C Programming  Language》

[1] void

附录A6.7:The(nonexistent) value of a void object may not be used in any way, and neither explicit nor implicit conversion to any non-void type may be applied. Because avoid expression denotes a nonexistent value, such an expression may be used only where the value is not required, for example as an expression statement or as the left operand of a comma operator.

An expression may be converted to type void by a cast. For example, a void cast documents the discarding of the value of a function call used as an expression statement.


[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变量

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. void  i;     //error: variable or field ‘i’ declared void  
当定义一个void变量时,编译程序会有以上的错误提示。

[2] (void)转换

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. char ch = 'a';  
  2. (void)0;  
  3. (void)ch;  
  4. int  i  = ((void)0, 2);  
  5. i   = (2, (void)0);     //error: void value not ignored as it ought to be  
  6. 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] (type) (void)0

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. i   = (int)( (void)0 );  // error: invalid use of void expression  
void对象不可以显示或隐式的转换为其它类型。

[4] 通用指针类型void*

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. int *pInt   = NULL;  
  2. char    *pChar  = NULL;  
  3. float   *pFloat = NULL;  
  4. struct _p_ch{  
  5.     void *pVoid;  
  6.     char ch;  
  7. }*p_ch;  
  8.           
  9. p_ch        = NULL;  
  10. p_ch->pVoid  = NULL;  
  11.           
  12. //void * can point any type of a pointer  
  13. p_ch->pVoid  = pInt;  
  14. p_ch->pVoid  = pChar;  
  15. p_ch->pVoid  = pFloat;  
  16. p_ch->pVoid  = p_ch;  
  17.           
  18. //void * can be pointed by any type of a pointer  
  19. pInt    = p_ch->pVoid;  
  20. pChar   = p_ch->pVoid;  
  21. pFloat  = p_ch->pVoid;  
  22. p_ch    = p_ch->pVoid;  
void*能够被任何类型(列举部分)的指针赋值,其它任何类型的指针也能被void *赋值。

[5] (type*) (void *)

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. int i       = 1;  
  2. int *pInt   = NULL;  
  3. pInt    = &i;  
  4. printf("%d\n", *( (int*)((void*)pInt) ) );  
指向任何对象的指针可以被转换为void *类型二不会丢失信息(但仍然不可引用一个void类型的值)。如果将结果再转换为初始指针类型,那么初始指针被恢复。

[6] ANSI与GNU

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. int i       = 1;  
  2. int *pInt   = NULL;  
  3. void *pVoid = NULL;  
  4. pInt    = &i;  
  5. pVoid   = pInt;  
  6. pVoid++;  
  7. pVoid--;  
  8. printf("%p, %p\n", pVoid, pInt );  
在GNU/Linux下,void *类型指针允许运算。某次运行,以上程序在Debian GNU/Linux下输入结果:0xbf819e7c, 0xbf819e7c。

<2> void和void*的应用场合

[1] void限定函数返回值和参数类型

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. add()  
  2. {  
  3.     return 2.1f + 3.0f;  
  4. }  
C中,无限定函数返回类型的情况下,函数默认返回整数类型。这样的函数在linux下调用会有一条警告:warning: data definition has no type or storageclass。在main函数中调用此函数并用printf()语句输出时得到结果5.当为此函数加了void限定符后,函数返回void类型值,再在程序中应用void时编译器就会报错。

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. void add()  
  2. {  
  3.     float f = 2.1f + 3.0f;  
  4. }  
  5.   
  6. int  main(void)  
  7. {  
  8.     add(6);  
  9.     Renturn 0;  
  10. }  
当函数没有任何参数时,不往函数参数列表类加”void”关键字时,像MDK-Keil就会警告,而Linux则不会。这个时候调用函数时,在GNU/Linux下再给函数参数时,程序正常运行。

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. void add(void)  
  2. {  
  3.     float f = 2.1f + 3.0f;  
  4. }  
  5.   
  6. int main(void)  
  7. {  
  8.     add(6);  
  9.     return 0;  
  10. }  
当函数参数无参数时加了void,调用函数再传递参数时编译器就会报错: error: too many arguments to function ‘add’。

[2] (void)0限定表达式被引用

MDK-ARM中审核参数的断言为,
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))  
  2.       
  3. /* Exported functions ------------------------------------------------------- */  
  4. 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指针十分有用,因为他给了程序员一种方法,表示某个特定的指针目前并未指向任何东西。
0 0
原创粉丝点击