C/C++中的NULL/nullptr到底是什么?

  • 操作系统在运行程序时,总是根据内存地址来查找该地址中存放的内容,继而进行运算的。所以,要知道,加载到内存中的所有数据都是存放在一定的地址中的。

1. 如下代码段,我们查看一下nullptr到底是什么?

点击查看代码
#include <iostream>

int main()
{
	void* var = nullptr;
	std::cin.get();
	return 0;
}
  • 解释一下上面一行普通的代码void* var = nullptr;,var是一个变量(只不过它是一个void*类型的指针变量),只要是变量,在内存中就会分配内存空间给这个变量,让它来存放数据。所以,变量var在内存中的地址是&var(0x005FFDBC),而这个变量的值是nullptr,也就是说在地址0x005FFDBC中存放了内容nullptr。
    image

  • 打开visual studio的菜单项,"调试"->"窗口"->"内存",可以看到地址0x005FFDBC的前4个字节(windows x86的指针变量占据的内存就是4个字节,x64的是8个字节,这个可以通过sizeof(void*)来验证)的内容是00 00 00 00,也就是说nullptr实际上就是值0。
    image

  • 当然,我们在写C++代码时,也可以使用NULL或0(容易产生阅读上的歧义),只不过在代码风格上可能会有些不统一,所以,一般而言,都是使用nullptr。NULL是一个宏定义,这个可以直接查看到,它也是0。
    image

2. nullptr/NULL既然都是0,那为什么一个数字0会容易引起程序崩溃?
这是因为当它代表或者转换( '#define NULL ((void*)0)'**** )为一个内存地址时,这个地址0x0000 0000在操作系统看来是一个无效的或者不存在的地址,所以,让OS从一个它认为无效的地址中取数据进行运算,就会出现访问冲突的问题,实际上并不是内存本身无效,而是OS认为它无效。

  • 如下代码,写一个简单的类Entity,然后通过类的对象entity_instance(= nullptr)来调用类的成员函数,来看看会引发什么问题?
点击查看代码
class Entity
{
public:
	Entity() = default;
	~Entity() {}
public:
	void printType()
	{
		std::cout << "Entity" << std::endl;
	}
	const std::string& getName() const
	{
		return m_Name;
	}
private:
	Entity* m_Parent;
	std::string m_Name;
};
int main()
{
	Entity* entity_instance = nullptr;
	std::cout << entity_instance->getName() << std::endl;
	std::cin.get();
	return 0;
}
  • 当我们调试运行到语句std::cout << entity_instance->getName() << std::endl;时,就会出现下面的访问冲突报错。为什么报错位置是0x4呢?应该怎么理解这个报错信息呢?(放在第4部分,地址偏移)
    image
  • 我们稍微改动一下代码,如下:
点击查看代码
int main()
{
	Entity* entity_instance = nullptr;
	entity_instance->printType();
	//std::cout << entity_instance->getName() << std::endl;
	std::cin.get();
	return 0;
}
- 结果发现程序可以运行,可以正常输出"Entity",没有任何报错,这是为什么呢?空指针怎么可以调用类的方法呢?

image

  • 原因在与,类的方法在实际编译之后,就会成为浮动函数,并不是我们所理解的在物理上属于Entity,Entity为空指针,就无法调用它的任何成员函数。因为,在编译后,该函数地址就存在了,无论运行时是不是创建了该类的对象。此时,上面Entity的两个成员函数,在编译之后,可以理解成如下代码:
点击查看代码
void printType(Entity* self)
{
	std::cout << "Entity" << std::endl;
}
const std::string& getName(const Entity* self)
{
	return self->m_Name;
}
  • 当调用printType(Entity* self)时,self指针并没有被使用,没有要去Entity的任何成员变量中读取信息;而在const std::string& getName(const Entity* self)中,self指针要去m_Name中读取信息(等同于nullptr(NULL or 0)->m_Name),此时,m_Name位于一个无效的地址(并没有创建Entity对象)上,所以,就出现了上面的报错:读取访问权限冲突, This is 0x4.

4. 地址偏移
宏 offsetof,用于查看一个变量相对于一个该类的一个偏移量,它接收两个参数,argu_1是类名,argu_2是变量名。
size_t off = offsetof(Entity, m_Name);,我们可以得到off的结果是4,如下截图代码,就是在找m_Name这个变量的地址。由于在m_Name的前面有Entity* m_Parent指针,所以,m_Name在偏移量为4的地方。当然,如果Entity被真实创建了,那么它的位置应该在new Entity + 4的位置。
image

posted @   小鬼1990  阅读(81)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示