前言
这是一个学习笔记,which 是我读的《C++ Primer Plus》。我想尽可能的将感觉有用的内容记录下来供以后来进行查阅。Whose前7章都是一些C语言的相关内容,大致扫过了一下,感觉没有什么 Value 就从第8章 start 写 note。希望自己能够将这本书看完。
1. inline函数
通过在函数声明的前方添加 inline 可以使得编译器生成内联的函数。函数内联是一种编译器的优化方式,对于函数体积较小的函数编译器一般会优化进而节约使用 call 指令进行调用的开销。体积大的函数即使 添加了 inline 关键字也不一定会被内联。
inline void func() //inline函数
我在此处记录的原因是 want(向,想,像这几个字太恶心了) 记录下一个有关宏(macro)展开的应用。下面充分说明宏展开的过程和函数调用的区别,which 只会进行替换而不考虑具体语义。
#define SQUARE(x) x * x
a = SQUARE(5.0); //is replaced by a = 5.0 * 5.0
b = SQUARE(4.5 + 7.5); //is replaced by b = 4.5 + 7.5 * 4.5 + 7.5
c = SQUARE(c++); //is replaced by c = c++ * c++
2. 引用变量(& 和 &&)
引用变量的一般特性就不进行赘述了,一般人都是了解的。这里我记录下我看书时学到的东西。
- & LValue Reference 符号进行左值引用,右边必须具有实体。
- && RValue Reference 符号进行右值引用,右边不必具有实体。
- * Dereference 符号进行解引用 。
左值引用就是我们一般理解的引用,而右值引用则是在 C++11 中新引入的一种方式,下面的列子很好的解释右值引用的作用,即实现移动语义(move semantics)。即 “=” 右边的数值不需要有具体的存储空间,会自动生成一个临时的空间来存放。
我个人理解这个实现的过程是:编译器现在栈中开辟一块空间,接着引用这个空间的内容,隐式的(implicit)分配空间。
//lvalue reference
double a = 1.0;
double &aref = a;
//example of && rvalue reference
double && rref = 6.0; //not allowed for double &
double & j = 15.0;
double && jref = 2.0 * j + 18.5; //not allowed for double &
cout << rref << endl; //display 6.0
cout << jref << endl; //display 48.5
另一个值得一说的是函数传递参数时的引用,当形参有 const 保留字进行修饰时一下情景(scenario)会生成临时参数在函数的栈中:
- 实参的类型正确,但不是左值(即没有具体存储空间)。
- 实参类型不正确,但可以转化为正确类型。
//there is a function possessing a referring argument
void func(const int &a);
func(1); //the fisrt scenrio, which don't have the left value. the argument is a instant number
double b;
func(b); //the second scenrio, which can transfer to the int
3. 函数
3.1 默认参数
C++ 为函数的使用引入的新的内容——默认参数。默认参数在实际进行程序设计时会减少程序员的工作量,in a way that 减少析构函数(destruct function)和方法(method)以及方法重载的数量。
默认参数遵守这样的规则:默认参数必须从右到左添加默认值。In other word,要为某个参数添加默认值则它右边的所有参数都必须有默认值。In my opinion,这样规定是为了区分什么时候使用默认值,也就是确定哪些参数没有传递实参。
// below is examples
// wrong default argument, because the right args don't have the default arg,
//this will cause the compiler can't make sure the argument belong to which arg
int func1(int a, int b=2, int c, int d);
// correct default argument
int func2(int a, int b=2, int c=3, int d=4);
func2(1); //same as func2(1, 2, 3, 4)
func2(1,312); //same as func2(1, 312, 3, 4)
3.2 函数重载
函数重载和多态两个术语指的是同一件事,即一个函数可以有多种形态,函数使用时最关键的就是函数名,因此就是多个同名函数。In 书中对这两个术语都进行了解释, 我将其融合,而最后书中选择了使用函数重载来进行使用。
函数重载的实现依赖于名称粉碎(mangling name),也叫名称修饰(decorated name)。编译器生成的二进制代码中是不允许出现同名函数的,不然就无法确定到底是调用了哪一个函数。而在C++中的同名函数也是存在差异的,即他们的函数声明不完全相同,要么是参数列表或者是返回值的类型不同,亦或命名空间(namespace)。利用这种特性编译器在生成代码时将函数的返回值,参数列表和命名空间缝合成一个全新的名字在二进制代码中使用。
Currently, in my view,there are 两种类型的名称粉碎方式:微软的名称修饰(主要是MSVC编译器使用)和 Itanium的名称粉碎(gcc和clang编译器使用)。
4. Template模板
4.1 模板的基本内容
对于模板中的内容,我并不关心模板背后实现过程中的匹配问题,这是编译器应该考虑的。这里我只是列出了使用过程中需要注意的内容。
对于模板的定义需要使用关键字 typename 开头以及 作为参数化类型(parameterized type),注:在C++98引入关键字 typename 之前这个位置是使用 class 的,所以在 template context 中这两者是等价的。
模板中有好多概念,如:具体化(specialization)、实例化(instantiation)和 隐式(implicit)、显示(explicit)。我们大部分使用的模板都是简单的使用方式,不会涉及到什么 显 / 隐实例化以及显示具体化。
下面就是具体的例子来说明上面概念的组合。最常用的定义方式就是
对于显示具体化 需要在 template 后添加 <>,而显示实例化则不需要。这些衍生的概念都是为了能够更好的进行模板匹配。而我们在使用时一般都会先进行实例化,如 std::vector< int > a;
template <typename T> //same as class
void Swap(T, T); //a normal template
template <> //same as typename
void Swap<double>(double, double); //a explicit specialization
template
void Swap<char>(char, char); //a explicit instantiation
4.2 类型和返回值问题
下面例子中是模板定义中一个常见的问题,变量类型的确定。由于并不知道T1 和 T2 的具体类型所以下方代码中的 ?type? 就无法事先确定好。
template <class T1, typename T2> //class is equal to typename in template scope
void func(T1 x, T2 y){
...
?type? xpy = x + y
...
}
为了解决上述问题,在 C++11 中引入了一个新的关键字 decltype,这个关键字可以根据表达式自动来推导出类型。
double x = 5.5;
double &rx = x;
const double *pd;
decltype(x) w; //w is type double
decltype(rx) u; //u is type double &
decltype(pd) v; //v is type const double *
另一个问题是函数返回值问题,这里返回值的类型 ?type? 应该是 decltype(x + y) 的类型,但是由于x + y声明是在 ?type? 后面,所以此时编译器无法使用 x + y 来进行类型推导。
template <typename T1, typename T2>
?type? func(T1 x, T2 y) {
return x + y
}
为了解决上述问题,在C++ 11 中引入了后置返回类型(trailing return type),which 使用 auto 作为类型的占位符,同时在函数声明的后方使用 ->type 来指向返回值类型的定义。上述问题就可以按照下面的方式进行解决。
template <typename T1, typename T2>
auto func(T1 x, T2 y) -> decltype(x + y) {
return x + y
}
4.3 std库中的模板
在C++的 std 库中有3种(我)常用的模板:std::string、std::vector和std::map。这里我 want 记录的不是如何使用他们,而是他们在二进制层面的特征。感谢 麦子zzy 提供的逆向内容。
std::string 不是模板,但它是一个非常重要的 std 类,所以我将还是决定将它的结构放置在这里。它自身有一个16字节的缓冲区,当字符串的长度小于15时会被放置在这个区域,而当字符串长度大于这个值时则会动态申请一片新的内存区域存放,使用 pStr 指针指向。
struct string {
union {
char buf[16];
char* pStr;
};
std::size_t nLength; //the length of the string
std::size_t nCapacity; //the capacity of the buf or pStr
};
std::vector 是 std 库中一个重要的模板,就数据结构而言它是简单的,简单到只使用3个指针就完成了所有内容。
struct vector {
void* pFirst; //pointer points to the fisrt element
void* pLast; //pointer points to the next of last element
void* pEnd; //pointer points to end of the buf
};
std::map,这是一个复杂的数据结构,背后的算法是红黑树。用到它的场景就是需要以 {key : value} 的形式来存放数据。它的核心就是下面的树节点。这个结构在逆向过程中最大的特点是在0x19(x64) 或 0xd(x86) 偏移处会被按照 byte 被 assign 为 1。
struct TreeNode {
struct TreeNode* pLeft; //0x00, left subtree, or smallest element if head
struct TreeNode* pParent; //0x08, parent, or root of tree if head
struct TreeNode* pRight; //0x10, right subtree, or largest element if head
char bColor = 1; //0x18,_Red or _Black, _Black if head _Red = 0 ,_Black = 1
char bIsnil = 1; //0x19, true only if head (also nil) node
struct Element ele;
};