C语言中void和NULL

本文详细解析了C语言中的void和void*概念,包括它们的定义、用途及应用场景。并通过实例阐述了void变量、void类型转换、void*指针的特性与使用规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


<4> 《Linux C语言编程一站式学习》

x86/GNU Linux/gcc平台下:
void *

在编程时经常需要一种通用指针可以转换为任意其它类型的指针,任意其它类型的指针也可以转换为通用指针。最初C没有void *通用指针类型,是把char*当通用指针,需要转换时就用转换运算符()。void *指针与其它类型的指针之间可以隐式转换,而不必用类型转换符。void*指针不能直接Dereference(访问),而必须转换成别的类型的指针才能做Dereference。而不能用void类型来定义变量(类型暂时不确定的变量),因为编译器不知道该分配几个字节给变量。


(2) void和void*的应用

在Debian GNU/Linux Desktop下编程。


<1> void 和void*的使用规则

接和各经典著作中对两者的描述,在linux下编程实践。

[1] void变量
void  i;     //error: variable or field ‘i’ declared void
当定义一个void变量时,编译程序会有以上的错误提示。

[2] (void)转换
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] (type) (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] (type*) (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’。

[2] (void)0限定表达式被引用
MDK-ARM中审核参数的断言为,
#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指针十分有用,因为他给了程序员一种方法,表示某个特定的指针目前并未指向任何东西。

(3) 《C语言常见问题集》

[空指针]

5.1:语言定义中说明, 每一种指针类型都有一个特殊值——“空指针” —— 它与同类型的其它所有指针值都不相同, 它“与任何对象或函数的指针值都不相等”。也就是说, 取地址操作符& 永远也不能得到空指针, 同样对malloc() 的成功调用也不会返回空指针, 如果失败, malloc() 的确返回空指针, 这是空指针的典型用法:表示“未分配”或者“尚未指向任何地方”的指针。


空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数; 而未初始化指针则可能指向任何地方。


如上文所述, 每种指针类型都有一个空指针, 而不同类型的空指针的内部表示可能不尽相同。尽管程序员不必知道内部值, 但编译器必须时刻明确需要那种空指针, 以便在需要的时候加以区分。

[NULL]

定义了预处理宏NULL (在<stdio.h> 和其它几个头文件中) 为空指针常数, 通常是0 或者((void *)0)。希望区别整数0 和空指针0 的人可以在需要空指针的地方使用NULL。使用NULL 只是一种风格习惯; 预处理器把所有的NULL 都还原回0, 而编译还是依照上文的描述处理指针上下文的0。特别是, 在函数调用的参数里, NULL之前(正如在0 之前) 的类型转换还是需要。

《C语言常见问题集》第五章都在解答关于NULL指针的问题。


(4) 《Linux C编程一站式学习》

[NULL]

x86/GNU Linux/gcc平台下:

NULL在C标准的头文件stddef.h中定义:#define  NULL ((void *))。就是把地址0转换成指针类型,称为空指针,它的特殊之处在于,操作系统不会把任何数据保存在地址0及其附近。也不会把地址0 ~ 0xffff的页面映射到物理内存,所以任何对地址0 的访问都会导致段错误。


C Note Over.
<think>好的,我现在要帮用户理解C语言NULLvoid*的区别及应用场景。首先,我需要回忆自己之前学过的相关知识,并参考提供的引用内容来确保准确性。 首先,NULL是什么?我记得NULL在C语言中是一个宏定义,通常用来表示指针,指向内存中无效的地址。根据引用[3][4],NULL用于指针变量初始化或表示指针不指向任何有效对象。比如在声明指针时,如果没有初始值,可以赋值为NULL,避免野指针的问题。例如,int *ptr = NULL; 这样ptr就不会指向随机的内存地址,使用前需要检查是否为NULL。 接下来是void*,也就是void指针。void*是一种通用指针类型,可以指向任意类型的数据,这在处理不确定数据类型时非常有用。比如在函数参数中,像memcpyqsort这样的函数,需要接受不同类型的指针,这时候用void*作为参数类型就能灵活应对。引用[1][2]提到,memcpy函数的原型是void *memcpy(void *dest, const void *src, size_t len),这里destsrc都是void*类型,允许传入任何类型的指针,但使用时需要进行强制类型转换。例如,当使用memcpy复制数据时,即使源目标是指向int或char的指针,也可以传递,因为void*可以接收任何指针类型。 然后,区别方面,NULLvoid*在类型作用上不同。NULL指针常量,而void*是一种指针类型。NULL用于表示指针不指向任何东西,而void*用于需要处理未知类型指针的情况。比如,当函数需要处理多种数据类型时,使用void*作为参数,而NULL则用于初始化或重置指针。 应用场景的话,NULL常用于初始化指针变量,函数返回指针,或在动态内存分配失败时返回NULL。例如,malloc函数在分配内存失败时会返回NULL,所以使用后需要检查指针是否为NULL。而void*常用于泛型编程,如内存操作函数(memcpy, memset)排序函数(qsort)的参数,允许这些函数处理不同类型的数据。引用[4]中的例子显示,void*可以接收其他类型的指针,但赋值时需要显式转换,比如int* x = (int*)p;。 需要注意的是,虽然void*可以指向任何类型,但在解引用前必须转换为具体类型。直接解引用void*指针会导致编译错误,因为编译器不知道其指向的数据类型大小。此外,NULL在C中通常定义为(void*)0,但在C++中可能有不同,但用户的问题集中在C语言,所以可以忽略C++的情况。 另外,引用[3]提到NULLvoid*nullptr的区别,但用户的问题是关于C语言,所以nullptr可能不在此讨论范围内,不过需要注意在C中NULLvoid*的使用方式。例如,NULL可以赋值给任何类型的指针变量,而void*需要显式转换才能赋值给其他类型的指针。 总结起来,用户需要明确NULL作为指针常量的用途,以及void*作为通用指针类型的应用场景。同时,区分两者的类型用法,避免混淆。例如,使用NULL来初始化指针,而void*用于需要处理多种数据类型的函数参数或返回值。此外,正确地进行类型转换,避免潜在的错误。</think>### C语言NULLvoid*的区别及用法解析 #### 1. **NULL的定义与作用** - **定义**:`NULL`是C语言中表示指针的宏定义,通常定义为`(void*)0`或`0`[^3][^4]。 - **用途**: - 初始化指针变量,表示未指向任何有效内存地址,例如: ```c int *ptr = NULL; ``` - 判断指针是否有效,例如: ```c if (ptr == NULL) { // 处理无效指针 } ``` - 函数返回指针时标识操作失败(如动态内存分配失败)[^1][^4]。 #### 2. **void*的定义与作用** - **定义**:`void*`是通用指针类型,可以指向任意数据类型,但**不能直接解引用**(需强制类型转换)[^2][^4]。 - **用途**: - 泛型编程:用于函数参数或返回值中处理未知类型的数据。例如: ```c void *memcpy(void *dest, const void *src, size_t n); ``` - 接收或返回任意类型的指针(需显式转换),例如: ```c int a = 10; void *p = &a; int *ptr = (int*)p; // 强制转换后才能使用 ``` #### 3. **核心区别** | 特性 | NULL | void* | |-------------|--------------------------|------------------------| | **类型** | 指针常量 | 通用指针类型 | | **赋值** | 可赋值给任意类型指针 | 需强制转换后才能赋值 | | **解引用** | 不可解引用(无意义) | 需转换类型后才能解引用 | | **应用场景**| 标识指针无效性 | 处理泛型数据 | #### 4. **典型应用场景** - **NULL的典型场景**: - 初始化指针变量避免野指针。 - 动态内存分配失败时返回`NULL`(如`malloc`函数)。 - **void*的典型场景**: - 内存操作函数(如`memcpy`、`memset`)的参数类型。 - 排序函数`qsort`的回调函数参数设计。 #### 5. **注意事项** - **NULL的安全性**:直接解引用`NULL`会导致段错误(Segmentation Fault)。 - **void*的转换**:使用`void*`时必须显式转换为具体类型,例如: ```c double *d = (double*)malloc(sizeof(double)); // 正确用法 ``` - **类型兼容性**:`void*`可以接收任意类型指针,但反向赋值需强制转换。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值