当前位置: 首页 > 科技观察

EasyC++16,指针探索之二

时间:2023-03-14 00:49:58 科技观察

大家好,我是梁唐。想要追求更好阅读体验的同学可以点击文末“阅读原文”访问github仓库。危险情况指针如果不小心使用,很容易导致一些意想不到的错误,因为它们可以操纵内存。C++Primer中给出了这样一个例子:int*ptr;*ptr=2333;在这段代码中,我们声明了一个int指针,并指向2333。但是,这里有个问题,我们在声明的时候并没有初始化这个指针。未初始化的指针不为空,但指向未知位置。如果它指向一个常量1200的地址,我们让它等于2333,那么当我们使用常量1200时,结果就是2333。更可怕的是,整个过程非常隐蔽,难以察觉。调试时可能会让人抓狂。所以永远不要修改未初始化指针指向的值。指针和数字C++Primer中给出了另一个示例。当我们输出指针时,得到的是一串十六进制数。那我们能不能反过来给指针赋一个十六进制数呢?整数*p;p=0xB8000000;答案是否定的,因为类型不一致。虽然我们打印指针的时候看起来是一个十六进制数,但是它的类型其实是指针类型,而不是整数类型。所以我们不能将一个整数赋值给一个指针。如果我们必须分配一个值,我们必须执行类型转换。整数*p;p=(整数*)0xB8000000;但是这样转换之后,显然又出现了一个问题。我们知道地址0xB8000000指向哪里吗?显然我们不知道,自然也说不清这里的值改变后会发生什么。什么结果。因此,尽管可以这样做,但强烈建议不要这样做。前面在new操作中提到过,使用指针的一个很大的好处就是可以在程序运行的时候动态分配内存。其实在C语言中也有类似的功能,可以使用malloc来分配内存。但是C++中有一个更好的运算符——新的。比如我们要动态创建一个int类型的变量,可以这样写:int*ptr=newint;new操作符根据后面的类型决定需要的内存大小,找到这样一块内存后返回地址。正好指针接收到的值是内存地址,这样的赋值操作正好可以完成。上面的代码也可以这样写:inta;int*ptr=&a;只不过在第二种写法中,除了可以使用指针ptr之外,还可以使用变量名a来访问这个int。但实际上两者的内部实现是完全不同的。我们直接通过变量名创建的变量的值会存放在栈内存中,而通过new创建的对象会存放在堆内存中。栈内存由系统自动分配,而堆内存则由程序员申请。两者的内存模型完全不同,我们会在后面的文章中详细讨论。目前我们简单理解的话,堆内存比较灵活,它的空间也比较大,可以存放比较大的数据。有了删除操作的动态创建,自然就有了动态删除,所以C++中就有了对应new的删除操作。delete操作符可以在变量使用后将内存归还到内存池中。因为在很多情况下,程序中的变量都是一次性使用的,或者是有生命周期的。当生命周期结束,任务完成,就没有必要继续占用资源了。毕竟系统中的内存资源是有限的,尤其是在一些大型项目或者嵌入式系统中,内存资源非常紧张。删除运算符后跟一个指针将释放指针指向的内存。int*ptr=newint;deleteptr;里面有很多陷阱,要小心。首先,使用new创建内存后,一定要记得delete,否则这块内存将永远被占用,无法释放。这种情况称为内存泄漏。另外,我们不能删除一个已经被删除的指针,这样也会导致严重的错误。C++Primer对此的描述是:Anythingcanhappen。当然不能再使用已经被删除的指针,这样会导致空指针错误。指针对于C++来说是一把双刃剑。对于Java、Python、Go等其他语言,内存回收的工作是由系统自动进行的。比如Java的JVM虚拟机就设计了严格的GC(garbagecollection)机制。程序员不需要关心内存回收,这一切都是由程序自动完成的。在C++中,此过程由程序员手动执行。从某种程度上来说,这当然很好。程序员具有高度的权限和灵活性。但这也是一个坑,尤其是在复杂的系统中,很难准确判断delete的执行时间。这会导致严重的问题,比如严重的内存泄漏,野指针乱飞等等……