update dir

This commit is contained in:
light-city 2019-07-25 18:45:55 +08:00
parent efc8b13385
commit b274475dba
10 changed files with 275 additions and 2 deletions

View File

@ -14,7 +14,7 @@
- [x] [纯虚函数和抽象类那些事](./abstract)
- [x] [vptr_vtable那些事](./vptr_vtable)
- [x] [virtual那些事](./virtual)
- [x] [volatile那些事](./volatile)
## 关于作者:
个人公众号:

View File

@ -86,3 +86,52 @@ int main()
1如果函数体内的代码比较长使得内联将导致内存消耗代价比较高。
2如果函数体内出现循环那么执行函数体内代码的时间要比函数调用的开销大。
## 2.虚函数virtual可以是内联函数inline
- 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
- 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
- `inline virtual` 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 `Base::who()`),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
```c++
#include <iostream>
using namespace std;
class Base
{
public:
inline virtual void who()
{
cout << "I am Base\n";
}
virtual ~Base() {}
};
class Derived : public Base
{
public:
inline void who() // 不写inline时隐式内联
{
cout << "I am Derived\n";
}
};
int main()
{
// 此处的虚函数 who()是通过类Base的具体对象b来调用的编译期间就能确定了所以它可以是内联的但最终是否内联取决于编译器。
Base b;
b.who();
// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
Base *ptr = new Derived();
ptr->who();
// 因为Base有虚析构函数virtual ~Base() {}),所以 delete 时会先调用派生类Derived析构函数再调用基类Base析构函数防止内存泄漏。
delete ptr;
ptr = nullptr;
system("pause");
return 0;
}
```

35
inline/inline_virtual.cpp Normal file
View File

@ -0,0 +1,35 @@
#include <iostream>
using namespace std;
class Base
{
public:
inline virtual void who()
{
cout << "I am Base\n";
}
virtual ~Base() {}
};
class Derived : public Base
{
public:
inline void who() // 不写inline时隐式内联
{
cout << "I am Derived\n";
}
};
int main()
{
// 此处的虚函数 who()是通过类Base的具体对象b来调用的编译期间就能确定了所以它可以是内联的但最终是否内联取决于编译器。
Base b;
b.who();
// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
Base *ptr = new Derived();
ptr->who();
// 因为Base有虚析构函数virtual ~Base() {}),所以 delete 时会先调用派生类Derived析构函数再调用基类Base析构函数防止内存泄漏。
delete ptr;
return 0;
}

BIN
inline/iv Executable file

Binary file not shown.

View File

@ -76,7 +76,15 @@ static成员函数不属于任何类对象或类实例所以即使给此函
**通常类成员函数都会被编译器考虑是否进行内联。 但通过基类指针或者引用调用的虚函数必定不能被内联。 当然,实体对象调用虚函数或者静态调用时可以被内联,虚析构函数的静态调用也一定会被内联展开。**
代码学习:[virtual_inline.cpp](./set3/virtual_inline.cpp)
- 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
- 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
- `inline virtual` 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 `Base::who()`),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
代码学习:
- [virtual_inline.cpp](./set3/virtual_inline.cpp)
- [inline_virtual.cpp](./set3/inline_virtual.cpp)
## 5.RTTI与dynamic_cast

View File

@ -0,0 +1,35 @@
#include <iostream>
using namespace std;
class Base
{
public:
inline virtual void who()
{
cout << "I am Base\n";
}
virtual ~Base() {}
};
class Derived : public Base
{
public:
inline void who() // 不写inline时隐式内联
{
cout << "I am Derived\n";
}
};
int main()
{
// 此处的虚函数 who()是通过类Base的具体对象b来调用的编译期间就能确定了所以它可以是内联的但最终是否内联取决于编译器。
Base b;
b.who();
// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
Base *ptr = new Derived();
ptr->who();
// 因为Base有虚析构函数virtual ~Base() {}),所以 delete 时会先调用派生类Derived析构函数再调用基类Base析构函数防止内存泄漏。
delete ptr;
return 0;
}

115
volatile/README.md Normal file
View File

@ -0,0 +1,115 @@
## 1.volatile
`volatile` 修饰的变量,在对其进行读写操作时,会引发一些**可观测的副作用**。而这些可观测的副作用,是由**程序之外的因素决定的**。
## 2.volatile应用
1并行设备的硬件寄存器如状态寄存器
假设要对一个设备进行初始化此设备的某一个寄存器为0xff800000。
```c++
int *output = (unsigned int *)0xff800000; //定义一个IO端口
int init(void)
{
int i;
for(i=0;i< 10;i++)
{
*output = i;
}
}
```
经过编译器优化后编译器认为前面循环半天都是废话对最后的结果毫无影响因为最终只是将output这个指针赋值为 9所以编译器最后给你编译编译的代码结果相当于
```c++
int init(void)
{
*output = 9;
}
```
如果你对此外部设备进行初始化的过程是必须是像上面代码一样顺序的对其赋值显然优化过程并不能达到目的。反之如果你不是对此端口反复写操作而是反复读操作其结果是一样的编译器在优化后也许你的代码对此地址的读操作只做了一次。然而从代码角度看是没有任何问题的。这时候就该使用volatile通知编译器这个变量是一个不稳定的在遇到此变量时候不要优化。
2一个中断服务子程序中访问到的变量
```c++
static int i=0;
int main()
{
while(1)
{
if(i) dosomething();
}
}
/* Interrupt service routine */
void IRS()
{
i=1;
}
```
上面示例程序的本意是产生中断时由中断服务子程序IRS响应中断变更程序变量i使在main函数中调用dosomething函数但是由于编译器判断在main函数里面没有修改过i因此可能只执行一次对从i到某寄存器的读操作然后每次if判断都只使用这个寄存器里面的“i副本”导致dosomething永远不会被调用。如果将变量i加上volatile修饰则编译器保证对变量i的读写操作都不会被优化从而保证了变量i被外部程序更改后能及时在原程序中得到感知。
3多线程应用中被多个任务共享的变量。
当多个线程共享某一个变量时,该变量的值会被某一个线程更改,应该用 volatile 声明。作用是防止编译器优化把变量从内存装入CPU寄存器中当一个线程更改变量后未及时同步到其它线程中导致程序出错。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出而不是使用已经存在寄存器中的值。示例如下
```c++
volatile bool bStop=false; //bStop 为共享全局变量
//第一个线程
void threadFunc1()
{
...
while(!bStop){...}
}
//第二个线程终止上面的线程循环
void threadFunc2()
{
...
bStop = true;
}
```
要想通过第二个线程终止第一个线程循环如果bStop不使用volatile定义那么这个循环将是一个死循环因为bStop已经读取到了寄存器中寄存器中bStop的值永远不会变成FALSE加上volatile程序在执行时每次均从内存中读出bStop的值就不会死循环了。
是否了解volatile的应用场景是区分C/C++程序员和嵌入式开发程序员的有效办法搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道这些都要求用到volatile变量不懂得volatile将会带来程序设计的灾难。
## 3.volatile常见问题
下面的问题可以看一下面试者是不是直正了解volatile。
1一个参数既可以是const还可以是volatile吗为什么
可以。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2一个指针可以是volatile吗为什么
可以。尽管这并不常见。一个例子是当一个中断服务子程序修该一个指向一个buffer的指针时。
3下面的函数有什么错误
```c++
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
```
这段代码有点变态其目的是用来返回指针ptr指向值的平方但是由于ptr指向一个volatile型参数编译器将产生类似下面的代码
```c++
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
```
由于*ptr的值可能被意想不到地改变因此a和b可能是不同的。结果这段代码可能返回的不是你所期望的平方值正确的代码如下
```c++
long square(volatile int *ptr)
{
int a=*ptr;
return a * a;
}
```
## 4.volatile使用
- volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。
- volatile 关键字声明的变量,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)
- const 可以是 volatile (如只读的状态寄存器)
- 指针可以是 volatile
代码学习:
- [noopt_vola.cpp](./noopt_vola.cpp)
- [volatile.cpp](./volatile.cpp)

15
volatile/noopt_vola.cpp Normal file
View File

@ -0,0 +1,15 @@
/* Compile code without optimization option */
#include <stdio.h>
int main(void)
{
const int local = 10;
int *ptr = (int*) &local;
printf("Initial value of local : %d \n", local);
*ptr = 100;
printf("Modified value of local: %d \n", local);
return 0;
}

BIN
volatile/nv Executable file

Binary file not shown.

16
volatile/volatile.cpp Normal file
View File

@ -0,0 +1,16 @@
/* Compile code with optimization option */
#include <stdio.h>
int main(void)
{
const volatile int local = 10;
int *ptr = (int*) &local;
printf("Initial value of local : %d \n", local);
*ptr = 100;
printf("Modified value of local: %d \n", local);
return 0;
}