0、0和数值“零”在指针上下文中不是一回事

2024-01-24 11:08

本文主要是介绍0、0和数值“零”在指针上下文中不是一回事,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

看林锐博士的《高质量C/CPP编程》附录的试卷,对空指针的判断居然强制要用NULL(如 if(p==NULL) ),后来从这篇文章看到一些东西觉得有点意思。不耐烦看的人看我的归纳:

0、0和数值“零”在指针上下文中不是一回事,0就是空指针,而不一定是“零”
1、用0还是NULL表示空指针是风格问题,而不是对与错的问题。
2、空指针真的有非零的,多是罕见机器。但此时 0 在指针上下文中会自动转为合适的空指针。
3、用 if(p), if(!p) 还是 if(p!=NULL), if(p==NULL) 都完全合法。
4、NULL一般被定义为0或(void*)0
5、0作为函数实参时,为了表示它是空指针,最好把它至于指针上下文中,即加上(char*)或(void*)修饰。(这要看编译器了,我在gcc4.1下就不需要修饰)。

=================================================
原文出处: http://c-faq-chn.sourceforge.net/ccfaq/
部分文摘
=================================================
6.2 怎样在程序里获得一个空指针?
根据语言定义, 在指针上下文中的常数 0 会在编译时转换为空指针。也就是说, 在初始化、赋值或比较的时候, 如果一边是指针类型的值或表达式, 编译器可以确定另一边的常数 0 为空指针并生成正确的空指针值。因此下边的代码段完全合法:

char *p = 0;
if(p != 0)

参见问题 5.3。

然而, 传入函数的参数不一定被当作指针环境, 因而编译器可能不能识别未加修饰的 0 ``表示" 指针。在函数调用的上下文中生成空指针需要明确的类型转换, 强制把 0 看作指针。例如, Unix 系统调用 execl 接受变长的以空指针结束的字符指针参数。它应该如下正确调用:

execl("/bin/sh", "sh", "-c", "date", (char *)0);

如果省略最后一个参数的 (char *) 转换, 则编译器无从知道这是一个空指针, 从而当作一个 0 传入。(注意很多 Unix 手册在这个例子上都弄错了。)

如果范围内有函数原型, 则参数传递变为 “赋值上下文", 从而可以安全省略多数类型转换, 因为原型告知编译器需要指针, 使之把未加修饰的 0 正确转换为适当的指针。函数原型不能为变长参数列表中的可变参数提供类型。 (参见问题 15.3) 在函数调用时对所有的空指针进行类型转换可能是预防可变参数和无原型函数出问题的最安全的办法。


============================
6.3 用缩写的指针比较 "if(p)" 检查空指针是否可靠?如果空指针的内部表达不是 0 会怎么样?
当 C 在表达式中要求布尔值时, 如果表达式等于 0 则认为该值为假, 否则为真。换言之, 只要写出

if(expr)

无论"expr" 是任何表达式, 编译器本质上都会把它当

if((expr) != 0)

处理。

如果用指针 p 代替 "expr" 则

if(p) 等价于 if(p != 0)。

而这是一个比较上下文, 因此编译器可以看出 0 实际上是一个空指针常数, 并使用正确的空指针值。这里没有任何欺骗; 编译器就是这样工作的, 并为、二者生成完全一样的代码。空指针的内部表达无关紧要。

布尔否操作符 ! 可如下描述:
!expr 本质上等价于 (expr)?0:1
或等价于 ((expr) == 0)
从而得出结论
if(!p) 等价于 if(p == 0)
类似 if(p) 这样的 "缩写", 尽管完全合法, 但被一些人认为是不好的风格 (另外一些人认为恰恰是好的风格; 参见问题 17.8)。

参见问题 9.2。

============================
6.4 NULL 是什么, 它是怎么定义的?
作为一种风格, 很多人不愿意在程序中到处出现未加修饰的 0。因此定义了预处理宏 NULL (在 <stdio.h> 和其它几个头文件中) 为空指针常数, 通常是 0 或者 ((void *)0) (参见问题 5.6)。希望区别整数 0 和空指针 0 的人可以在需要空指针的地方使用 NULL。

使用 NULL 只是一种风格习惯; 预处理器把所有的 NULL 都还原回 0, 而编译还是依照 上文的描述处理指针上下文的 0。特别是, 在函数调用的参数里, NULL 之前 (正如在 0 之前) 的类型转换还是需要。问题 5.2 下的表格对 0 和 NULL 都有效 (带修饰的 NULL 和带修饰的 0 完全等价)。

NULL 只能用作指针常数; 参见问题 5.7。

============================
6.14 说真的, 真有机器用非零空指针吗, 或者不同类型用不同的表达?
至少 PL/I, Prime 50 系列用段 07777, 偏移 0 作为空指针。后来的型号使用段 0, 偏移 0 作为 C 的空指针, 迫使类似 TCNP (测试 C 空指针) 的指令明显地成了现成的作出错误猜想的蹩脚 C 代码。旧些的按字寻址的 Prime 机器同样因为要求字节指针 (char *) 比字指针 (int *) 长而臭名昭著。

Data General 的 Eclipse MV 系列支持三种结构的指针格式 (字、字节和比特指针), C 编译器使用了其中之二:char * 和 void * 使用字节指针, 而其它的使用字指针。

某些 Honeywell-Bull 大型机使用比特模式 06000 作为 (内部的) 空指针。

CDC Cyber 180 系列使用包含环 (ring), 段和位移的 48 位指针。多数用户 (在环 11 上) 使用的空指针为 0xB00000000000。 在旧的 1 次补码的 CDC 机器上用全 1 表示各种数据, 包括非法指针, 是十分常见的事情。

旧的 HP 3000 系列对字节地址和字地址使用不同的寻址模式; 正如上面的机器一样, 它因此也使用不同的形式表达 char * 和 void * 型指针及其它指针。

Symbolics Lisp 机器是一种标签结构, 它甚至没有传统的数字指针; 它使用 <NIL, 0> 对 (通常是不存在的 <对象, 偏移> 句柄) 作为 C 空指针。

根据使用的 ``内存模式", 8086 系列处理器 (PC 兼容机) 可能使用 16 位的数据指针和 32 位的函数指针, 或者相反。

一些 64 位的 Cray 机器在一个字的低 48 位表示 int *; char * 使用高 16 位的某些位表示一个字节在一个字中的偏移。


============================
6.7 如果 NULL 和 0 作为空指针常数是等价的, 那我到底该用哪一个呢?
许多程序员认为在所有的指针上下文中都应该使用 NULL, 以表明该值应该被看作指针。另一些人则认为用一个宏来定义 0, 只不过把事情搞得更复杂, 反而令人困惑。因而倾向于使用未加修饰的 0。没有正确的答案。 (参见问题 9.2 和 17.8) C 程序员应该明白, 在指针上下文中 NULL 和 0 是完全等价的, 而未加修饰的 0 也完全可以接受。任何使用 NULL (跟 0 相对) 的地方都应该看作一种温和的提示, 是在使用指针; 程序员 (和编译器都) 不能依靠它来区别指针 0 和整数 0。

在需要其它类型的 0 的时候, 即便它可能工作也不能使用 NULL, 因为这样做发出了错误的格式信息。(而且, ANSI 允许把 NULL 定义为 ((void *)0), 这在非指针的上下文中完全无效。特别是, 不能在需要 ASCII 空字符 (NUL) 的地方用 NULL。如果有必要, 提供你自己的定义

#define NUL '/0'

这篇关于0、0和数值“零”在指针上下文中不是一回事的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/639496

相关文章

VMWare报错“指定的文件不是虚拟磁盘“或“The file specified is not a virtual disk”问题

《VMWare报错“指定的文件不是虚拟磁盘“或“Thefilespecifiedisnotavirtualdisk”问题》文章描述了如何修复VMware虚拟机中出现的“指定的文件不是虚拟... 目录VMWare报错“指定的文件不是虚拟磁盘“或“The file specified is not a virt

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非

C和指针:字符串

字符串、字符和字节 字符串基础 字符串就是一串零个或多个字符,并且以一个位模式为全0的NUL字节结尾。 字符串长度就是字符串中字符数。 size_t strlen( char const *string ); string为指针常量(const修饰string),指向的string是常量不能修改。size_t是无符号数,定义在stddef.h。 #include <stddef.h>

【Python知识宝库】上下文管理器与with语句:资源管理的优雅方式

🎬 鸽芷咕:个人主页  🔥 个人专栏: 《C++干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 文章目录 前言一、什么是上下文管理器?二、上下文管理器的实现三、使用内置上下文管理器四、使用`contextlib`模块五、总结 前言 在Python编程中,资源管理是一个重要的主题,尤其是在处理文件、网络连接和数据库

【C++】作用域指针、智能指针、共享指针、弱指针

十、智能指针、共享指针 从上篇文章 【C++】如何用C++创建对象,理解作用域、堆栈、内存分配-CSDN博客 中我们知道,你的对象是创建在栈上还是在堆上,最大的区别就是对象的作用域不一样。所以在C++中,一旦程序进入另外一个作用域,那其他作用域的对象就自动销毁了。这种机制有好有坏。我们可以利用这个机制,比如可以自动化我们的代码,像智能指针、作用域锁(scoped_lock)等都是利用了这种机制。

MFC中App,Doc,MainFrame,View各指针的互相获取

纸上得来终觉浅,为了熟悉获取方法,我建了个SDI。 首先说明这四个类的执行顺序是App->Doc->Main->View 另外添加CDialog类获得各个指针的方法。 多文档的获取有点小区别,有时间也总结一下。 //  App void CSDIApp::OnApp() {      //  App      //  Doc     CDocument *pD

C和指针:结构体(struct)和联合(union)

结构体和联合 结构体 结构体包含一些数据成员,每个成员可能具有不同的类型。 数组的元素长度相同,可以通过下标访问(转换为指针)。但是结构体的成员可能长度不同,所以不能用下标来访问它们。成员有自己的名字,可以通过名字访问成员。 结构声明 在声明结构时,必须列出它包含的所有成员。 struct tag {member-list} variable-list ; 定义一个结构体变量x(包含

71-java 导致线程上下文切换的原因

Java中导致线程上下文切换的原因通常包括: 线程时间片用完:当前线程的时间片用完,操作系统将其暂停,并切换到另一个线程。 线程被优先级更高的线程抢占:操作系统根据线程优先级决定运行哪个线程。 线程进入等待状态:如线程执行了sleep(),wait(),join()等操作,使线程进入等待状态或阻塞状态,释放CPU。 线程占用CPU时间过长:如果线程执行了大量的I/O操作,而不是CPU计算

hot100刷题第1-9题,三个专题哈希,双指针,滑动窗口

求满足条件的子数组,一般是前缀和、滑动窗口,经常结合哈希表; 区间操作元素,一般是前缀和、差分数组 数组有序,更大概率会用到二分搜索 目前已经掌握一些基本套路,重零刷起leetcode hot 100, 套路题按套路来,非套路题适当参考gpt解法。 一、梦开始的地方, 两数之和 class Solution:#注意要返回的是数组下标def twoSum(self, nums: Lis