列举一些ReadLine的键绑定,凡是使用Readline控件的程序中都可以使用这些快捷键,比如bash、lftp、gdb等。首先,做一下约定:
- \C-a 表示 Ctrl+a
- \M-a 表示 Meta+a Meta键 在 PC 中通常为 ALT键或 ESC键
熟读而精思,循序而渐进,厚积而薄发。
列举一些ReadLine的键绑定,凡是使用Readline控件的程序中都可以使用这些快捷键,比如bash、lftp、gdb等。首先,做一下约定:
char ** s; s是一个指针1,指向一个指针2, 指针2指向char const char ** s; s是一个指针1,指向一个指针2,指针2指向char,char是常数 char * const * s; s是一个指针1,指向一个常量1,常量1是个指针2,指针2指向char const char * const * s; s是一个指针1,指向一个常量1,常量1是个指针2,指针2指向char,char是常数 const char * const * const s; s是一个常量1,常量1是一个指针1,指针1指向一个常量2,常量2是个指针2,指针2指向char,char是常数
最后,感谢Felix的精彩解析!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <iostream> //~ #include <string> using namespace std; class demo { public: void* operator new(unsigned int size) //~ operator new must return "void *" { //~ do something like the following demo* ptr = ::new demo[size]; return ptr; } }; int main() { demo * ptr = new demo[9]; return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <iostream> using namespace std; template <class T> class Base { public: void set(int n){m_data = n;} int get(){return m_data;} private: int static m_data; }; template <class T> int Base<T>::m_data = 0; class Derived1: public Base<Derived1>{}; class Derived2: public Base<Derived2>{}; int main() { Derived1 D1; Derived2 D2; D1.set(1); D2.set(2); cout<<D1.get()<<ends<<D2.get()<<endl;//~ 输出1 2 return 0; } |
其实,这只是一个小把戏,用不同类型实例化后的类完全是两个类型了,当然会拥有不同的静态成员!
1 2 3 4 5 6 7 8 9 10 11 | int main() { S * pS = new S; D1 * pD1 = pS; D2 * pD2 = pS; pS->foo(); //~ OK, 存在一条访问路径B->D1->S pD1->foo(); //pD2->foo(); //~ 'B::foo' : no accessible path to private member declared in virtual base 'D2' return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <iostream> //~ #include <string> using namespace std; class Base { public: virtual void foo() { cout<<"Base::foo()"<<endl; } }; class Derived: public Base { private: void foo() { cout<<"Derived::foo()"<<endl; } }; int main() { Derived * pD = new Derived; Base * pB = pD; pB->foo(); //~ OK, call Derived::foo() //~ pD->foo(); //~ oops, Derived::foo() is private! return 0; } |
可见,权限检查发生在名字查找之时,而虚函数表的访问不带有权限检查。
没有放在程序结束处,而是放在的代码段的起始位置,更甚,由于程序是从start开始执行的,似乎永远不会执行到这两行,程序也永远不会结束。但是这个程序却能正常结束,而且是由我们的这两行代码来结束执行的。
其中的猫腻就在于短转移指令jmp short s1。jmp指令是用来更改寄存器CS:IP来实现程序的转移的,汇编语言中一般是以标号来指明跳转的目的地址的。对于长转移指令,例如jmp dword ptr ds:[0],CPU确实是以目标地址的段基址和段内偏移来修改CS:IP的。但是对于短转移来说,却是利用当前IP与目标地址之间的的偏移量来确定的。上例中,执行jmp short s1时,IP指向下一条指令nop,与目标地址s1之间偏移量为10:
CS:IP 机器码 汇编指令(编译器将标号替换了) 0B80:0018 B80000 MOV AX,0000 ;s1 0B80:001B CD21 INT 21 0B80:001D B80000 MOV AX,0000 0B80:0020 EBF6 JMP 0018 ; jmp short s1 0B80:0022 90 NOP
1 2 3 4 5 6 7 8 9 10 | #include <iostream> using namespace std; int main() { Derived * pD = new Derived; pD->foo(); return 0; } |

这里面有三张函数表,分别对应d的三个基类。事实上,d的虚函数d()的地址保存在了基类B1对应的虚函数表内。从多重继承的内存布局,我们可以看到子类新加入的虚函数被加到了第一个基类的虚函数表,所以当dynamic_cast的时候,子类和第一个基类的地址相同,不需要移动指针,但是当dynamic_cast到其他的父类的时候,需要做相应的指针的移动。
比较下面两段代码:
1 2 3 4 | for(int i = 0; i < n; i++) { //~ do something; } |
1 2 3 4 | for(int i = 0; i < n; ++i) { //~ the same thing; } |