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。
-
打开visual studio的菜单项,"调试"->"窗口"->"内存",可以看到地址0x005FFDBC的前4个字节(windows x86的指针变量占据的内存就是4个字节,x64的是8个字节,这个可以通过sizeof(void*)来验证)的内容是00 00 00 00,也就是说nullptr实际上就是值0。
-
当然,我们在写C++代码时,也可以使用NULL或0(容易产生阅读上的歧义),只不过在代码风格上可能会有些不统一,所以,一般而言,都是使用nullptr。NULL是一个宏定义,这个可以直接查看到,它也是0。
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部分,地址偏移)
- 我们稍微改动一下代码,如下:
点击查看代码
int main()
{
Entity* entity_instance = nullptr;
entity_instance->printType();
//std::cout << entity_instance->getName() << std::endl;
std::cin.get();
return 0;
}
- 原因在与,类的方法在实际编译之后,就会成为浮动函数,并不是我们所理解的在物理上属于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
的位置。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?