前言
上次接触c++还是大一…那时看的飞快,现在发现c++的各种需求接踵而来….于是决定这俩个月狠过一遍c++ primer。如果进度允许的话再看看c++的技巧与模式。
主要从以下几方面入手:
- 语法,c++的语法较为复杂,需要比较细节的记录
- 底层实现,每章对应的语法点看下其反汇编的实现,着重逆向工程方面的问题。汇编以ARMv8与x86-64平台为准,编译器ndk-r16b,linux下为gcc7.3.0,gdb+ida分析
- 设计模式,因为涉及到开发,因此特记下。主要分俩点:c++专有的设计问题(代码结构,文件内容、头文件规划等);面向对象共有的设计模式(GOF)。
以primer5为基准,一章一篇记录
编译器选用
gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。对于c文件,g++内部调用gcc完成编译,二者等价。对于cpp,gcc的链接可能出问题,但g++内部仍用gcc完成编译,再自己链接。
gcc是一个套件集,其中编译c++的为g++。为保证编译c++时链接c++的库,编译c++应该用g++。
实验确实gcc不能自动链接c++的库
第一章简记
- linux用echo $? 看返回值
- iostream库是std标准库的,使用后链接了libstdc++.so。std为标准库的命名空间
- <<运算符:左侧ostream对象,返回值为左侧对象,输出;>>运算符:左侧istream对象,返回值为左侧对象,输入。
- endl是操纵符特殊值,其效果结束当前行,并刷新关联设备的缓冲区。防止流停于内存
- 作用域运算符::指明使用的命名空间中的名字。
- 标准库定义了不同版本的<<>>运算符,来处理不同类型的右值对象
- 忽略空格与换行回车,因此表达式等的换行与直接写后面没区别。运用此规则提高代码可读性
- 不允许/**/嵌套。
- 语句块也是语句的一种-符合一般的文法
- for的初始化部分可以定义变量,作用域为for内
- 键盘的输入结束符linux下为ctrl+d,若判断条件为istream时,效果为检测流的状态,遇到无效输入与终止为无效状态
- .h文件用于声明,.cpp文件用于定义。定义时分配空间。声明为了一遍编译单元时确定符号类型。注意.h实际上被预处理时完全包含进来的。一个.cpp作为一个编译单元。具体规则可以参考:http://www.cnblogs.com/ider/archive/2011/06/30/what_is_in_cpp_header_and_implementation_file.html 具体的规范,语法知识增多后自己确认,并参考好的c++项目实现与相关规范书籍
- 文件重定向>与< 命令行下控制标准输入输出流
- .运算符左对象右成员。使用类型与struct时不用加struct类型前缀了
- 时刻谨记,编译单元是一个.cpp文件。编译为.o目标(ELF)文件。之后进行连接生成运行或.so。.a是.o的打包。而预处理是在一个编译单元内各自展开处理的。因此头文件的保护宏是为了在一个编译单元内仅被声明定义一次。所以包含的头文件一定会在编译单元中,加入保护宏后防止重复声明定义了。而对于依赖的头文件include的位置,如果放在.h文件中容易使其它单元使用该头文件时含多余的展开,因此除了头必须依赖的.h,其它的.h都放在.cpp文件内
- c/c++的使用模块除了标准库与运行库,都是要手动链接时选择的…因此每个单元(.cpp)编译成.o后链接要手动选择使用的库。java等语言import与导包后会进行一起打包,运行时加载模块使用。.h就是提供接口声明的,导出的部分声明都在.h中
第15条解释了当时我的.l文件应以yylval不正确的问题..在.y内宏定义是不能在另一编译单元内使用的。后来的union将其定义在了.h文件因此可用….yylvalunion中找不到定义的问题可能是lex未导入node头在yacc的头前
反编译
第一章文件:
1.cpp
Android.mk
Application.mk
:=是附加的意思
X86-64
指令简记
64位寄存器
栈始终是满减
分析
看下elf:
依赖:
入口为start:
静态链接与动态链接的 native 程序的入口函数都是 _start。动态链接的程序在执行 _start 之前,需要先由 linker 加载依赖库。
不过动态与静态的___libc_init不同。这里默认都是动态链接的了
main:
f:
got表填入外部的数据,got.plt表填入外部函数。std::cout放在got中
ARM
指令简记ARMv8
ARM共有31个通用寄存器和2个特殊寄存器,都是64位。31个通用寄存器用X0到X30来表示,两个特殊寄存器是SP和ZR。
STP X9, X8, [X4]:存储X9到X4的地址,存储X8到X4+8的地址。都是8字节。LDP反之
Xn表示64位寄存器。Wn表示32位寄存器
FP(x29)寄存器保存栈帧地址,LR(x30)保存当前过程的返回地址
ZR:0寄存器,零寄存器忽略所有对它的写操作,并且所有对它的读操作都返回0.您可以在大多数(但不是全部)指令中使用零寄存器。
BL:LR存返回地址跳转
ADRP:先进行PC+imm(偏移值)然后找到lable所在的一个4KB的页,然后取得label的基址,再进行偏移去寻址。用于大范围寻址
STR与STUR的区别是偏移正与负
分析
start
main:
f:
ARMv8下的栈帧始终为X29分割记录区与数据区,sp指向栈顶。
此外armv8下的外部模块数据与函数全在got,没见到plt