C++总复习(一)

C++总复习

1.虚拟地址空间布局

C++总复习(一)

(1)保留区的作用?

​ 保留区的目的是不可访问的区域(不允许读写),128兆大小,放入的是c的库,C语言的基本库,操作系统基本函数(操作系统调用)

(2)命令行参数:

int main(int argc,char *argv[],…)

从屏幕中获取命令

(3)共享库的位置为什么不在保留区?(保留区不可修改)

库是提供声名的地方,为什么不放入保留区?原因只有一个:保留区的大小太小,共享库太大;

(4)堆和栈的区别?

​ 1)虚拟内存空间布局

​ (管理方式,生长方向,空间大小,分配方式,分配效率,分配后的系统响应,碎片问题)

**管理方式:**栈由编译器自动管理;堆由程序员控制,使用方便,但易产生内存泄露;

空间大小:(栈的大小一般是2M到10M,ulimit -s 查看系统栈内存大小)空间大小:栈顶地址和栈的最大容量由系统预先规定(通常默认2M或10M);堆的大小则受限于计算机系统中有效的虚拟内存,32位Linux系统中堆内存可达2.9G空间。

生长方向:

​ 栈是从高地址向低地址扩展的连续内存,内存都是连续的,速度快,但是程序不可控;

​ 堆的内存是从低地址向高地址扩展的,但是内存不一定连续,开辟的效率低一点,容易产生内存碎片,灵活高;系统用链表存储空闲地址空间,自然不可连续,而链表就是从低地址向高地址遍历的;

分配方式:

​ 栈内存是由系统分配,系统释放的;以函数为单位进行栈内存分配,函数栈帧,局部变量,形参变量等都存放在栈内存上。堆内存是由用户自己分配的,C 语言用 malloc/free进行分配释放,C++用 new/delete 进行分配释放,由于堆需要用户自己管理,因此堆内存很容易造成内存泄露,而栈内存不会。

分配效率:

​ 栈由计算机底层提供支持:分配专门的寄存器存放栈地址,压栈出栈由专门的指令执行,因此效率较高。堆由函数库提供,机制复杂,效率比栈低得多。

分配后的系统响应:

​ 只要栈剩余空间大于所申请空间,系统将为程序提供内存,否则报告异常提示栈溢出。

​ 操作系统为堆维护一个记录空闲内存地址的链表。当系统收到程序的内存分配申请时,会遍历该链表寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链 表中删除,并将该结点空间分配给程序。若无足够大小的空间(可能由于内存碎片太多),有可能调用系统功你能去增加程序数据段的内存空间,以便有机会分到足够大小的内存,然后进行返回。,大多数系统会在该内存空间首地址处记录本次分配的内存大小,供后续的释放函数(如free/delete)正确释放本内存空间。此外,由于找到的堆结点大小不一定正好等于申请的大小,系统会自动将多余的部分重新放入空闲链表中。

内存碎片问题:

​ 栈不会存在碎片问题,因为栈是先进后出的队列,内存块弹出栈之前,在其上面的后进的栈内容已弹出。

​ 而频繁申请释放操作会造成堆内存空间的不连续,从而造成大量碎片,使程序效率降低。可见,堆容易造成内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和内核态切换,内存申请的代价更为昂贵。所以栈在程序中应用最广泛,函数调用 也利用栈来完成,调用过程中的参数、返回地址、栈基指针和局部变量等都采用栈的方式存放。所以,建议尽量使用栈,仅在分配大量或大块内存空间时使用堆 。 使用栈和堆时应避免越界发生,否则可能程序崩溃或破坏程序堆、栈结构,产生意想不到的后果。

​ 2)数据结构

​ 堆(数据结构):堆可以被看成是一棵树,如:堆排序;

​ 栈(数据结构):一种先进后出的数据结构。

2.编译链接原理

(1)预编译 .c/.cpp->.i

​ 1)展开宏,文本替换

​ 2)处理头文件(方法的定义点),递归展开的

​ 3)处理预编译指令,#if 0 #endif

​ 4)删除注释

​ 5)添加行号和标识

​ 6)保留#pragma 预编译无法处理,交给编译器处理

(2)编译 .i->.s

​ 1)词法分析

​ 将源程序扫描,对代码进行标记(eg:数字不能作为命名的开头)

​ 关键词、标识符、数字、字符串、加减乘除等于等等;

​ 2)语法分析

​ 建立语法树C++总复习(一)

​ 3)语义分析

​ 结合上下文简历语法树

​ 4)代码优化

​ 使用一些合适的寻址方式,使用位移代替乘法,删除多余的指令—效率高

​ 整个过程是机器在操作—不可控,会出现错误

​ 5)生成汇编代码(指令)

(3)汇编 .s->.o

​ 翻译汇编代码

​ 生成ELF格式的文件

​ 1.没有处理强弱符号?

​ 对于C/C++而言,编译器默认函数和已初始化的全局变量为强符号,而未初始化的全局变量为弱符号,在编程者没有显示指定时,编译器对强弱符号的定义会有一些默认行为,同时开发者也可以对符号进行指定,使用"attribute((weak))"来声明一个符号为弱符号。

​ 定义一个相同的变量,当两者不全是强符号时,gcc在编译时并不会报错,而是遵循一定的规则进行取舍:

  • 当两者都为强符号时,报错:redefinition of ‘xxx’
  • 当两者为一强一弱时,选取强符号的值
  • 当两者同时为弱时,选择其中占用空间较大的符号,这个其实很好理解,编译器不知道编程者的用意,选择占用空间大的符号至少不会造成诸如溢出、越界等严重后果。

​ 2.符号表的生成(有部分符号表放入了und区域,生成了外部符号)

​ 3.而程序的入口地址也是不确定的,虚假地址和虚假偏移

(4)链接 处理汇编遗留下来的问题

​ 1)强弱符号的处理–合并符号表

​ 2)符号解析(处理虚假地址和虚假偏移)

​ 3)分配地址和空间

​ 程序—虚拟地址的映射

​ 4)符号的重定位

​ 虚拟地址–物理地址

(5)运行

​ 1)创建虚拟地址空间和内存映射(PCB)

​ 分页机制和分段机制(创建页目录,页表将程序加载到内存中去)

​ 2)加载指令和数据

​ 3)程序的入口地址,写入下一行指令寄存器

3.C/C++区别-----函数符号生成规则

c:和函数名相关

c++:函数返回值+函数名+函数形式列表

规则:

​ int Sum(int a,int b); int : H double : N

​ ?Sum(函数名)@@YA(调用约定)H(函数返回值)HH(函数形式参数)@z

4.c/c++区别-----函数重载

c语言无函数重载

c++函数函数重载的三要素?

​ 1.函数同名

​ 同名函数有三种关系:重定义(重载)、隐藏、重写(覆盖)

​ 2.不同的参数列表

​ 3.同一作用域

5.内联函数

在调用点直接把代码展开,无开栈清栈的开销,效率高

缺点:代码目标,有递归和循环不得使用(函数的堆栈开销《函数执行开销 建议)

以空间换取时间

inline函数都写在。h文件中,方便找到定义点(只在relaease版本生效,debug和普通函数想同)

1)与普通函数的区别:

在调用点直接把代码展开,无开栈清栈的开销,效率高

inline只在本文件中可见

2)与static函数的区别:

static修饰的函数会把符号从globe变成local(都在本文件中可见)

3)与宏的区别

宏的处理在预编译阶段,无类型检查和安全检查

内联的处理实在编译阶段处理,有类型和安全检查

6.c++引用

引用是变量的别名,

底层:以指针方式实现

1.引用一定要进行初始化

2.引用不能改变

3.引用是不能使用被引用的数据和不能被取地址的数据

4.引用变量只能使用所引用的内存单元

7.malloc/new的区别

1.new是c++开辟内存使用

​ malloc是c开辟内存使用

2.new是一个关键字,开辟的大小由系统计算

​ malloc是一个函数原型:void *malloc(size_t) 形参的大小由sizeof人为计算

3.malloc返回的是void类型,不安全(void是一个半开半闭的区间,通过类型强转后得到大小,不安全)

​ new可以使用类型接收 安全

4.malloc不能对堆进行初始化操作

​ new可以对堆内存进行初始化

5.new对数组直接传入的的是数组的个数即可

​ malloc则要仅算出数组整体的带下

6.开辟内存空间的位置

​ malloc开辟的是堆的内存

​ new开辟的是自由空间存储区(可以在堆上)

7.开辟失败的反馈

​ new抛出异常(bad_alloc) malloc返回值为NULL

8.new底层是以operator new实现的

​ 在对象中可以进行函数重载(内存池的实现),malloc不允许

8.const的用法

1.c语言 const修饰的变量–变成常变量

​ 在编译阶段查看是否做左值? 做–报错

2.c++ const修饰的变量为–常量

编译器:把用到常量的地方替换成敞亮初始化的值

​ 1)常量必须做初始化操作

​ 2)常量不允许被修改(间接修改和直接修改)

3.用法

​ 1)修饰全局变量–变量符号从globe变成local只在本文件可见

​ 2)修饰局部变量–变成常量

​ 3)修饰成员变量–不依赖对象 在类外初始化

常方法–const TYPE* const this

常对象只能调用常方法

9.c/c++相互调用:

c++调用c

​ extern “c”{ c语言规则的代码 }

c语言调用c++

区分:所有c++源文件中自带宏_Cplusplus,以此来识别是c或者c++文件

10.面向对象编程oop思想

1.实体(属性和行为)—》ADT(抽象说明)(属性和行为)–》转换为类(类变量和属性)–》抽象成对象(对象和行为)

2.c++三大特性

​ 封装:(该让你看到的让你看到 不让你看到的不让你看)

​ 隐藏了对象属性和实现细节,对外提供共有接口供函数调用

​ pubic:都能看到 protected:本类和子类 private:本类

3.this指针

​ 调用了this call调用约定

​ 隐藏参数this指针,const类类型

​ Student *const this 确定那个对象调用的函数

11.类中的默认函数(以string类为例)

1.构造函数

​ 1.开辟内存

​ 2.初始化对象所占的内存空间

特点:可重载

​ 不依赖对象调用

人为提供:系统就不会提供(提供的是不带参数构造函数)

2.析构函数

​ 特点:不可重载

​ 依赖对象(释放资源——调用析构)

​ 释放空间

3.拷贝构造函数

​ 已存在的对象去生成相同类型的新对象

​ string (const string& rhs)

​ 为什么里面是引用?避免对象重复生成、栈溢出

​ 默认的拷贝构造函数是浅拷贝,释放资源时会释放同一片内存,系统崩溃

​ (有成员变量指针存在就要考虑深拷贝的实现)

​ 浅拷贝是共享资源 深拷贝是独立资源

4.赋值运算符重载函数

​ 用已生成的对象去生成相同类型的已存在的对象

​ if(this!=&rhs) {

​ //处理 要删除delete[] mptr;//释放旧的资源

​ }

5.取地址运算符的重载函数

string* operator&()

{

return this;

}

6.const修饰的取地址运算符的重载函数

const string* operator&()const

{

​ return this;

}

12.static关键字

1.static修饰全局变量和局部变量

​ 全局变量属于数据,不是放在.data 就是在.bss 如果是常量字符串放在.rodata段

​ static修饰是的全局变量从globe变成local。静态函数不能被其它文件所用;其它文件中可以定义相同名字的函数,不会发生冲突;

​ 函数中普通局部变量在栈中开辟内存,stattic修饰后在全局数据区分配内存,使得其作用域变长(它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,也就是不能在函数体外面使用它(局部变量在栈区,在函数结束后立即释放内存);),静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化

静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0(局部变量不会被初始化);

2.static修饰普通函数

​ 本文件可见 其他文件不可见 限制了其作用域 同时符号从globe变成loacl

3.static修饰成员变量和成员方法

​ 修饰成员变量

​ 改变了其生存周期 不再依赖对象 在类外初始化 不在使用this call调用约定‘

​ 静态数据成员被当作是类的成员,由该类型的所有对象共享访问,对该类的多个对象来说,静态数据成员只分配一次内存。静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。

​ 使用的时候要加以类的作用域,不属于对象 属于类

​ 修饰成员方法

​ 改变了其生存周期 不再依赖对象 在类外初始化 不在使用this call调用约定

​ 不能访问普通成员变量和方法 在类外实现

13.模板(函数模板和类模板)

模板编译阶段:

​ 1.定义点:编译了模板的头部

​ 2.在调用点编译了模板实例化的头部

1.函数模板

​ template(typename T)

T Sum(T a,T b)

{

​ return a+b;

}//函数模板

int Sum(inyt a,int b)

{

​ return a+b;

}//模板函数

从函数模板变成模板函数是模板的实例化过程

模板的实参推演:

​ 系统会识别传递的参数,但是会有一定的限制条件 所有均要满足定义点

(不能产生二义性 要用实参)

模板特例化(全特化和局部特例化)–char*类型 针对

模板重载(普通函数版本 模板版本 模板的特例化)

​ 调用先后顺序(1》3》2)

问题:为什么要把模板的整个定义写在一个.h文件中?

1)链接的时候,需要实例化模板,这时候就需要找模板的具体实现了。假设在main函数中调用了一个模板函数,这时候就需要去实例化该类型的模板。注意main函数里面只包含了.h文件,也就是只有模板的声明,没有具体实现。就会报错。

2)而模板的实现.cpp里面,虽然有模板的具体实现,但是没有谁在该.cpp里面使用一个模板函数,就不会生成一个具体化的实例。
ps:模板是在需要的时候,才会去生成一个具体化的实例的,比如,你只要一个int型的实例,模板就只会给你生成一个int型的实例,模板本身是不会被执行的(也就是模板本身不产生汇编指令),是模板生成的具体化实例才产生指令(这个实例是隐藏的,我们是看不到的)