核心简记
按p5的子章节记
函数基础
- 定义包括:返回类型、函数名字、形参、函数体
- 调用运算符()作用于表达式,表达式是函数或指向函数的指针。实参列表,用实参初始化函数的形参。调用表达式的类型为函数返回类型
- 函数的返回值用于初始化调用表达式的结果。
- 实参是形参的初始值,但并没有规定实参的求值顺序。编译器可以任意顺序对实参求值。
- 形参名是可选的。可以不写形参名,只靠函数原型:参数个数、类型、返回类型、函数名就可以确定符号表内函数的位置。模块连接时由这些信息生成导出符号表内符号名供使用。声明时经常不写
- 作用域:指符号名字的作用范围,编译时由符号表的表现形式确保。生命周期:指执行过程中对象的存在时间。
- 作用域内隐藏外,编译器实现为符号表的查找方式。生命周期依赖定义方式。
- 自动变量:普通局部变量,分配在栈中。由函数自动创建销毁。
- 局部静态对象:第一次经过对象定义语句时初始化,函数结束不会影响它。默认为值初始化0
- 头文件声明,源文件定义。定义的源文件应该把声明的头文件包含进来,这样可以利用编译器检查定义与声明的匹配。
- 一个.c对应一个.o为一个编译单元-一个模块,之后模块连接。
对函数的调用等价:
参数传递
- 形参的初始化原理与变量初始化一样,形参的类型决定了形参与实参的交互方式:引用类型的形参将绑定实参。否则为拷贝初始化
- 初始化过程中,会忽略顶层const。因为const的初始化传入可以为普通。因此定义int i与const int i的形参会函数重复定义
- 底层const不可忽略,常量引用可接受范围比引用广,因为普通引用不可接受常量。这是底层const
- 传递数组时实际传递的是首元素指针。形参的第一维将被忽略掉,见下。传递大小常用:标记、尾后指针、大小参数
- 多维数组时,数组的第二维及之后的大小都是数组类型一部分,也是指针类型一部分。不能省略
- 参数数量未知类型相同可用initializer_list类型的形参接受,但传入还需要{}。c的…形参也支持。接受单一的initializer_list类型参数,此函数可以传入一个花括号列表,而c的实现需要前面的参确定类型,依据类型大小可以从栈中取出其它参使用。
|
|
返回类型与return
- 返回一个值的方式与初始化一个变量的方式一样,返回的值用于初始化调用点的一个临时量,该临时量就是调用结果。
- 不要返回局部变量的引用与指针。因为栈调用后释放
- 返回引用的函数结果是左值,其它为右值。
- c++11规定可以返回{}包围的值列表,用于对临时量初始化。
- c++11可以使用尾置返回类型:auto f()->int (*)[10]
函数重载
- 编译时编译器自动指定调用,参数类型、数量不同即可。
- 顶层const会被忽略,无法重载仅顶层const不同的。底层const可区分
- 结果:找到最佳匹配、无匹配、二义性匹配。
- 调用时,编译器先寻找函数名的声明,之后忽略外层同名,之后检查调用有效验证重载。编译原理中为:先找符号表,找到后不再找外层同名的了,之后该表内查找有效匹配。因此内层是会屏蔽外层重载的。同层作用域才可重载。
函数匹配:
- 可见->名字->参数量->参类型
- 可见与名字找到可见同一作用域下的重载函数,参数数量匹配,可以有默认参数,参数类型可以转化。
- 寻找最佳匹配,当多个形参匹配时:如果有且只有一个函数满足-每个实参的匹配不劣于其它可行函数的匹配、至少一个实参的匹配优与其它函数的匹配。如果没有函数符合则为二义性
- 类型匹配:精确匹配(类型相同、数组-函数转化对应指针、顶层const改动) > const转化实现匹配 > 通过类型提升实现匹配 > 通过算数类型转化 > 类类型转化
语言特性
默认实参
声明时可以写成:
一个赋予其后也需要。调用时前必须完整不可省略,后可以省。
不可重复声明默认实参
内联函数、constexpr函数
定义时
编译时展开,有的函数不能展开,编译器可以忽略这个inline
定义时:
函数返回值及形参是字面值类型、只有一条return语句。返回不一定是常量表达式,一切都有编译器检查。只是一个可能的标志。
内联函数、constexpr函数都是定义时的概念,且应该处处一致,因此这俩个通常定义在头文件中。
调试帮助
- assert断言,预处理宏,求值为0则输出并终止程序,定义于cassert
- NDEBUG,定义该预处理变量则关闭调试帮助,assert无用
编译器提供的调试时变量:
函数指针
函数指针指向函数,函数的类型由返回类型与形参类型决定。
当把函数名作为值使用时,自动转化为指针。使用指针可以直接调用函数,无需解引用。
指针类型必须与重载精确匹配才可赋予。
函数指针或函数类型可以作为形参,此时会自动转化为指针。
函数指针与函数类型是不同的,decltype可以区分出来,当decltype作用于函数名时返回的是函数类型,想作为指针要加*。
返回函数类型不会自动转化为指针,必须显式将返回类型指定为指针。
可以理解为函数名为地址,函数指针为指针,函数类型为返回值、形参相关的类型
|
|
反汇编
application
源代码:
x86-64
指令简记
ida结构体操作:支持文件导入、直接c式写入、自己一个字节一个字节定义。不过最后都要定义在structures中才可以。
使用定义好的结构体:找到定义的地址(数据区或栈区)Edit->Struct Var(ALT+Q)命令显示一组已知的结构体,之后选择就好,自动扩散。
X64多了8个通用寄存器:R8、R9、R10、R11、R12、R13、R14、R15。都是64位的,可以使用R8、R8D、R8W、R8B访问全64位、低32位、低16位、低8位
时刻谨记:b一字节,w(word)-俩字节,dw-4字节,qw-8字节
分析
main:
fstruct
fclass
fmore
大意是一个全局的mutex和一个cond来保护一个锁变量,锁变量再来保护目标变量。锁变量的第一个字节表示目标变量是否被初始化过了,第二个字节表示目标变量是否在初始化中。
freturnvector
总结下:
- 结构体当做大块的空间整体处理
- 传参为结构体:先在调用者的栈顶创建好结构体的拷贝,被调用函数使用时用正偏移直接使用调用者栈中的结构体拷贝对象,相当于栈中传参,只是sp提前变好。
- 返回为结构体:调用时第一个传入返回对象的地址,函数内返回的内容直接向其拷贝
- 传参为类对象:先在调用者的栈中进行拷贝构造,之后将构造好的对象地址传入。被调用者使用的参数对象实际在调用者栈中,调用结束后调用者执行析构函数。
- 返回为类对象:调用时第一个传入返回对象的地址,函数内直接对其构造。
- 函数的传参顺序:左到右:di、si、dx、cx、8d、9d 之后sp低到高放
- 静态局部变量的初次初始化依靠锁机制。
- 以上的sp在debug模式下都是提前分好的,调用传参不用现分配。
总而言之,对象形参都是调用者准备好的,之后清理也是由调用者负责。被调用者栈中其实没有传来的大块内容,只传入所需地址。而返回值也是直接传入的调用者栈中的地址,直接对调用者栈拷贝返回。
因此参数的构造由调用者负责,都在sp之上。sp不用回收参数,因为一开始分配栈帧时算参数的大小进去了。析构也由调用者负责。返回值的构造由被调用者负责。
ARM-64
指令简记
FP(x29)寄存器保存栈帧地址,LR(x30)保存当前过程的返回地址。
UXTB:字节被无符号扩展(高位清0)
分析
main
fstruct
fclass
fmore
freturnvector
arm-v8的传参顺序:寄存器01234567 栈顶
如果返回值为对象或结构体,提前在调用方分配好栈空间,将地址以X8传入被调函数,被调函数针对该指针构造返回,这个空间就是返回的临时值。同样传入对象或结构体也存于调用者栈中,传入指针。
c++对结构体与类的处理还是不太一样。结构体更偏向于数据,类更偏向于操作this