C++
(1)C++编译的过程
① 预编译:预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令,包括宏定义指令、条件编译指令和头文件包含指令。
宏定义指令,如#define Name TokenString等。 预编译所要做的是将程序中的所有Name用TokenString替换。(宏替换)
条件编译指令,#ifdef,#ifndef,#else,#elif,#endif等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。
头文件包含指令,如#include <stdio.h>,#include "animal.h"等。 将所有的 include 语句替换为头文件的实际内容。
同时预处理过程还会删除注释行,添加行号和文件名标识,便于编译时编译器产生编译错误或警告时能够显示行号。(.i文件)
② 编译:编译是将文本文件转换成汇编代码的过程。(.s文件)
③ 汇编:汇编阶段是把编译阶段生成的汇编语言代码翻译成二进制目标代码(也就是二进制机器指令)。(.o文件)
④ 链接:将 .o文件生成 .exe执行文件。
- 静态链接:在程序执行之前就完成链接工作,也就是等链接完成后文件才能执行。但是这有一个明显的缺点,比如说库函数,如果文件A 和文件B都需要用到某个库函数,链接完成后他们连接后的文件中都有这个库函数,当A和B同时执行时,内存中就存在该库函数的两份拷贝,这无疑浪费了存储空间。当规模扩大的时候,这种浪费尤为明显。静态链接还有不容易升级等缺点。为了解决这些问题,现在的很多程序都用动态链接。
- 动态链接:和静态链接不一样,动态链接是在程序执行的时候才进行链接。也就是当程序加载执行的时候。还是上面的例子 ,如果A和B都用到了库函数 fun(),A和B执行的时候内存中就只需要有 fun() 的一个拷贝。
(2)#include <file.h>和 #include “file.h” 的区别
<>:表示系统自带的头文件,编译器先从系统目录下开始搜索,然后再搜索PATH环境变量所列出的目录。
" ": 表示用户自定义的头文件,编译器从当前目录搜索,然后再搜索系统目录和PATH环境变量所列出的目录。
(3)C++程序的内存分为哪几个区?
① 栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
② 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
③ 全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放 。
④ 文字常量区:存放字符串常量。 程序结束后由系统释放 。
⑤ 程序代码区:存放函数体的二进制代码。
【内存的分配方式有几种? 】
一、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,例如全局变量。二、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
三、从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
【堆和栈的区别】
在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数以及运算符new分配得到的就是在堆上。
当一个函数调用完返回后它会释放该函数中所有的栈空间。栈是由编译器自动管理的。堆是动态分配内存的,并且你可以分配使用很大的内存。但是用不好会产生内存泄漏。并且频繁地malloc和free会产生内存碎片,因为分配动态内存是寻找匹配内存的。而用栈则不会产生碎片。
在栈上存取数据比通过指针在堆上存取数据快些。
(4)内存泄漏怎么产生的?
内存泄漏一般是指堆内存的泄漏,也就是程序在运行过程中动态申请的内存空间不再使用后没有及时释放,导致那块内存不能被再次使用。
由于栈上的内存的分配和回收都是由编译器控制的,所以在栈上是不会发生内存泄露的,只会发生栈溢出(Stack Overflow),也就是分配的空间超过了规定的栈大小。
(5)如何避免内存泄漏?
① 不要手动管理内存,可以尝试在适用的情况下使用智能指针。
② 使用string而不是char* 。string类在内部处理所有内存管理,而且它速度快且优化得很好。
③ 除非要用旧的lib接口,否则不要使用原始指针。
④ 在C++中避免内存泄漏的最好方法是尽可能少地在程序级别上进行new和delete调用。任何需要动态内存的东西都应该隐藏在一个RAII对象中,当它超出范围时释放内存。RAII在构造函数中分配内存并在析构函数中释放内存,这样当变量离开当前范围时,内存就可以被释放。
(注:RAII资源获取即初始化,也就是说在构造函数中申请分配资源,在析构函数中释放资源)
⑤ 使用了内存分配的函数,要记得使用其想用的函数释放掉内存。可以始终在new和delete之间编写代码,通过new关键字分配内存,通过delete关键字取消分配内存。
⑥ 培养良好的编码习惯,在涉及内存的程序段中,检测内存是否发生泄漏。
(6)智能指针如何解决内存泄漏?
解决内存泄漏最有效的办法就是使用智能指针(Smart Pointer)。使用智能指针就不用担心这个问题了,因为智能指针可以自动删除分配的内存。智能指针和普通指针类似,只是不需要手动释放指针,而是通过智能指针自己管理内存的释放,这样就不用担心内存泄漏的问题了。
C++里面的四个智能指针: ① auto_ptr;② unique_ptr;③ shared_ptr;④ weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用,包含在头文件 <memory> 中
智能指针指向NULL,那么保存的东西就会释放,因为它是智能的,指向了一个新对象、新东西,会释放原来的内存去存新的东西,那么指向空就相当于析构了,普通指针不行,不会自己析构指向的东西(即保存的东西)则可能会导致内存泄漏。
智能指针则可以在退出作用域时,不管是正常离开或是因异常离开,总调用delete来析构在堆栈上动态分配的对象。
智能指针(smart pointer)的通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。每次创建类的新对象时,初始化指针就将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。
传指针相当于新建了一个指针,但是仍然指向那个地址,那个内存。传引用相当于把内存直接传过去。传指针引用,相当于指针没变,仍然指向那个地址,那个内存。
(7) 野指针
野指针,也就是指向不可用内存区域的指针。如果对野指针进行操作,将会使程序发生不可预知的错误,甚至可能直接引起崩溃。 野指针不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是野指针是很危险的,也具有很强的掩蔽性,if语句对它不起作用。
造成野指针的常见原因有三种:
1、指针变量没有被初始化。
任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
char *p; //此时p为野指针
2、指针指向的内存被释放了,而指针本身没有置NULL。
对于堆内存操作,我们分配了一些空间(使用malloc函数、calloc函数或new操作符),使用完后释放(使用free函数或delete操作符)。指针指向的内存被释放了,而指针本身没有置NULL。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用。因为即便p不是NULL指针,它也不指向合法的内存块。所以在指针指向的内存被释放后,应该将指针置为NULL。
char *p=new char[10]; //指向堆中分配的内存首地址,p存储在栈区
cin>> p;
delete []p; //p重新变为野指针
3 、指针超过了变量的作用范围。
即在变量的作用范围之外使用了指向变量地址的指针。这一般发生在将调用函数中的局部变量的地址传出来引起的。这点容易被忽略,虽然代码是很可能可以执行无误,然而却是极其危险的。局部变量的作用范围虽然已经结束,内存已经被释放,然而地址值仍是可用的,不过随时都可能被内存管理分配给其他变量。
char *p=new char[10]; //指向堆中分配的内存首地址
cin

最低0.47元/天 解锁文章
497





