C++对象模型小探索

class base {
public:
    base():a(10),b(11){}
    virtual void fa(){ std::cout << "base a function...\n";}
    virtual void fb(){ std::cout << "base b function...\n";}
    
public:
    int a;
    int b;
    std::string c{"angel"};
};

class derived: public base {
public:
    void fa() override { std::cout << "derived a function ...\n";}
    void fb() override { std::cout << "derived b function...\n"; }
    void fc() { std::cout << "derived c function...\n";}
public:
    int c;
};



int main(int argc, char** argv)
{
    std::cout << "class base size : " << sizeof(base) << std::endl;
    std::cout << "class derived size: " << sizeof(derived) << std::endl;
    
    derived* pd = new derived();
    base* pb = new derived();
 
    using func= void(*)();
    
    long int vptr1 = reinterpret_cast<long int>(pb);
    long int vptr2 = reinterpret_cast<long int>(pd);
    
    long int* p = (long int*)vptr1;
    long int* p1 = (long int*)vptr2;
    std::cout << "p  " <<  p << std::endl;
    std::cout << "p1 " <<  p1 << std::endl;
    
    long int* vtaddr = (long int*)(*p);
    long int* vtaddr2 = (long int*)(*p1);
    std::cout << "vtaddr  " << vtaddr << std::endl;
    std::cout << "vtaddr2 " << vtaddr2 << std::endl;
    
    
    func* ptr = (func*)(vtaddr);
    (*ptr)();
    func* ptrb = (func*)(vtaddr+1);
    (*ptrb)();
    
    
    func* bptr = (func*)(vtaddr2);
    (*bptr)();
    func* bptrb = (func*)(vtaddr2+1);
    (*bptrb)();
    
    std::cout << *(int*)(long int*)(vptr1+8) << std::endl;
    std::cout << *(int*)(long int*)(vptr1+12) << std::endl;
    std::cout << std::string((char*)(long int*)(vptr1+16))<< std::endl;
    
    
    pd->fa();
    pb->fa();
    
    

    return 0;
}


以上代码强制调用vtable指向的函数,从安全性角度讲:可以替换虚表的任何函数。

结果是:

 class base size : 40
 class derived size: 48
 p  0x102a84240
 p1 0x102a84210
 vtaddr  0x10000b090
 vtaddr2 0x10000b090
 derived a function ...value is ...34
 derived b function...
 derived a function ...value is ...-1746446496
 derived b function...
 10
 11
 
 angel
 derived a function ...value is ...10
 derived a function ...value is ...10

强行使用裸指针调用,得到的成员值明显不对。推测是没有代入隐含的this指针。

下次谈谈如何基于本案列做C++的逆向工程。

我对C/C++语言运算规则另类小解

先看看无聊的C运算符优先级和结合性。看到表1的人可以分为三类:

  • C语言编译器的作者
  • 计算机入门者
  • 各种计算机基础能力考试应对者

比如要你计算: -Num++, +Num++, f( a(), b(), c()) ,int a= c,d,e; 之类的运算顺序。

大多数工程师为了避免记忆这繁琐的规则,使用()括号大法来解决大部分运算符优先级/结合性的问题。而且在大多数情况下,商业团队会通过统一的编程规范来避免程序语言的”坑”。

表1:运算符优先级和结合性

优先级 运算符 描述 结合性
1 ++ -- 后缀自增与自减 从左到右
() 函数调用
[] 数组下标
. 结构体与联合体成员访问
-> 结构体与联合体成员通过指针访问
(type){list} 复合字面量(C99)
2 ++ -- 前缀自增与自减[注 1] 从右到左
+ - 一元加与减
! ~ 逻辑非与逐位非
(type) 转型
* 间接(解引用)
& 取址
sizeof 取大小[注 2]
_Alignof 对齐要求(C11)
3 * / % 乘法、除法及余数 从左到右
4 + - 加法及减法
5 << >> 逐位左移及右移
6 < <= 分别为 < 与 ≤ 的关系运算符
> >= 分别为 > 与 ≥ 的关系运算符
7 == != 分别为 = 与 ≠ 关系
8 & 逐位与
9 ^ 逐位异或(排除或)
10 | 逐位或(包含或)
11 && 逻辑与
12 || 逻辑或
13 ?: 三元条件[注 3] 从右到左
14[注 4] = 简单赋值
+= -= 以和及差赋值
*= /= %= 以积、商及余数赋值
<<= >>= 以逐位左移及右移赋值
&= ^= |= 以逐位与、异或及或赋值
15 , 逗号 从左到右

去记忆这种表格明显是反直觉的,我们还是得理解编译器帮我们解释符号的意图。

C的符号可以归结为几类:

  1. 算术运算符。最普通的+ – * / % ~ & | ^ << >>
  2. 逻辑运算符。! && ||
  3. 赋值运算符。= &= <<=
  4. 比较运算符。< == >= <= > !=
  5. 成员访问。 [] () * -> .
  6. 其他 … , (type强转), sizeof, _Alignof

先是结合性,原则很简单,也很人类。

就是按照普通人的阅读顺序去记忆: 比如[], 它肯定是从左到右结合。因为 一个表达式 a[1], 先要知道a,才能知道相对寻址的 a[1]。 再比如算术运算a + b, 肯定是先move a到寄存器,在把a和b想加。因此也是从左向右结合。

那么,哪些是从右往左结合呢?

我们写代码 sizeof( SuperStruct ), 肯定是先想到SuperStruct的大小和地址在哪,然后再去取大小。这就是很直接的计算机思维

最有代表性的是: 前++,前– VS 后++, 后–

用他们来测试下这种规律的正确性:

a++ , 从左到右。因为先得知道a,才能++。

–a , 从右往左结合。 因为先得知道a,才能–。

看,这是不用记忆的。非常直接的计算机符号逻辑

优先级的记忆

优先级最高的: 除了后加/后减的优先级超高。 成员访问优先级高完全符合直觉。成员访问优先级高,可以理解为得先有数据才能做各种运算

优先级次高的: 几乎全部单目运算符优先级是次高的。单目运算符只需要一个操作数,和成员访问有相同的属性。

优先级第三/四高: 接下来是双目运算符, 乘/除/取余(优先级三) 比 加减(优先级四)优先级高,符合直觉。

优先级第五/六高: 算术运算比关系运算优先级高。 加减乘除移位比所有比较符号优先级高。移位这种算术运算 >>, << 比 >,< >=,<= 的符号优先级高。先算出来再比较。

优先级第六/七高: >=, <= 比 ==, !==优先级高。这个没有什么规则。就是C语言编译器的人自己定的规则。 先比较大小,再比较是否相等。

优先级第八/九/⑩高: 位运算自己的比较,依次是 & ^ |

优先级第十一/十二: 逻辑运算 && ||

第十三: 孤零零的一个 三目。

第十四:各种赋值

第十五:孤独的逗号。

所以,总结起来规则是:

  1. 成员运算优先级最高。成员运算符算是单目的一种,所以剩下的单目运算符次高。(单目优先原则,直觉)
  2. 算术运算符比关系比较运算符高。(先算[包括移位]出来再比较,直觉)
  3. 关系比较运算符比位运算符高。(比较完再来做位运算,非直觉)
  4. 位运算比逻辑运算符高。(有运算结果再来做逻辑与或)
  5. 逻辑运算比赋值运算符高。(做完算术,比较,位和逻辑运算再来赋值)
  6. 逗比排在最后。
  7. 结合规则完全从直觉去判断。

Lockless stack

The core logic of lockless programming is the CAS operation brought by processor. CAS can compare and exchange value between two number in a single instruction. From the perspective of CPU synchronization primitive, CAS instruction like “cmpxchg” is expensive. but from the view of application programmer, the lockfree data structure is really attractive for its free user from using more expensive operation system “mutex locks” which will switch the worker thread from background back and forth.

LockFree data-structure always comes with ABA problem, according herb sutter. for example, We need to resolve the deletion issue: we can’t delete a node if a concurrent reader/writer might be pointing to it.

there are 4 options to solve the problem:

  • Option1: Use lazy garbage collection. solve the problem because memory cannot be reused while any pointers to it exist, However, destruction of nodes becomes nondeterministic.
  • Option2: Use reference counting ( garbage collection). solve the problem in cases without cycles.
  • Option3: Never actually delete a node ( only logically delete) , can work when deleting is rare.
  • Option4: Put auxiliary nodes in between actual nodes. Contains a next pointer only, no data. These links don’t move. Enables operations on adjacent nodes to run without interference.

STL Review

159436 share 352353.56 bonus 70470.71 tax 281882.85 actual

STL六大组件:1,容器。2,算法。3,迭代器。4,仿函。 5,适配器。6,分配器

顺序容器 Vector:

1,  分配的内存是以2的倍数动态增长。内存不够时,分配空间是当前的2倍。
    同时将原来的内容复制到新的空间。消耗较大。
    vector的allocator实现二级空间配置器,二级配置直接管理内存池, 伙伴系统,自由链表组成。
   
 
2,  连续存储空间,插入删除较慢。
3, 使用vector<int>.swap(vector_to_be_clear)来消除内存。
使vector_to_be_clear成为临时对象,释放内存。->小技巧。 
4, clear只会把vector的size清为零,不会释放内存。
5, vector是随机迭代器的顺序容器。
6, 随机存取优势。插入劣势。
7, 和dque相比,删除元素不会释放空间,重新申请空间时有额外的元素拷贝操作。
8,  access方法: [] , at,  front, back, data
    modifier方法: 
     assign, 
     push_back(), 
     pop_back(), 
     insert, 
     erase, 
     swap, 
     clear, 
     emplace, 
     emplace_back()

    capacity方法: 
     size, 
     max_size, 
     resize,  
     capacity, 
     empty, 
     reserve, 
     shrink_to_fit 

顺序容器 Deque:

1, 支持随机访问迭代器。
2, deque的内存空间分布是小片的连续,小片间用链表相连。
3, deque的重新分配要比vector快。重新分配后原有元素不需要拷贝。
4, 随机存取优势。插入劣势。 
5, 和vector相比,删除元素释放空间,重新申请空间没有额外元素拷贝操作。
6,  access方法: 
      [],
      at, 
      front, 
      back

    modifier方法: 
      assign, 
      push_back(), 
      push_front(), 
      pop_back(), 
      pop_front(), 
      insert, 
      erase,
      swap,  
      clear,  
      emplace, 
      emplace_front, 
      emplace_back

顺序容器 List:

1, 双向链表,内存空间不连续。
2, List支持双向迭代器。
3, 很好的支持任意地方插入和删除。
4, 频繁插入优势。存取劣势。
5, 方法:
      push_back(element)
      pop_back()  

关联容器 Map:

1,  map不保存数据时,每个节点占用 2个指针+1个枚举的空间。 
2, map是一个有序的key排列,提供双向迭代器。(非严格意义的平衡二叉树)。 
3, 插入相同的key会返回不成功。如果想支持使用multimap以支持1:N的存储能力。 
4, access: [], at     
    modifier: insert, erase, swap, clear, emplace,     emplace_hint     
    observer: key_comp, value_comp     
    operation:  find, count, lower_bound, upper_bound, equal_range     
    allocator: get_allocator

关联容器 unordered_map

 1, 方法buckets:
     bucket_count
     max_bucket_count
     bucket_size
     bucket

     Hash策略:
     load_factor
     max_load_facotr
     rehash
     reserve

     Observer:
     hash_function
     key_eq
     get_allocator

     Modifier:
     emplace
     emplace_hint
     insert
     erase
     clear
     swap

     元素查找:
     find
     count
     equal_range

     元素访问:
     at
     []

关联容器 Set:

1, 底层使用红黑树,同map
2, 默认对元素进行升序排列。
3, 不能直接修改key。需要先删除后插入。
4,  方法上就是红黑树的抽象方法,同map。
    capacity: empty, size, max_size
    modifier: insert, erase, swap, clear, emplace, emplace_hint
    observer: key_comp, value_comp
    operation: find, count, lower_bound, upper_bound, equal_range
    allocator: get_allocator

容器适配器 queue

1, 以标准STL容器deque为基础。
2, FIFO功能。
3,  方法: empty, size, front, back, push, pop, emplace,swap

容器适配器 stack

1, 以标准STL容器deque为基础。
2,FILO功能。
3, 方法: empty, size, top, push, emplace, pop, swap

顺序容器 array

1, 定长,定类型。
2, capacity方法: size, max_size(),empty
    access方法: [], at, front, back, data
    modifier:  fill, swap