diff --git a/README.md b/README.md index 08adbfd..fc0b11b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - [x] [纯虚函数和抽象类那些事](./abstract) - [x] [vptr_vtable那些事](./vptr_vtable) - [x] [virtual那些事](./virtual) - +- [x] [volatile那些事](./volatile) ## 关于作者: 个人公众号: diff --git a/inline/README.md b/inline/README.md index 9fde997..3030e27 100644 --- a/inline/README.md +++ b/inline/README.md @@ -86,3 +86,52 @@ int main() (1)如果函数体内的代码比较长,使得内联将导致内存消耗代价比较高。 (2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。 + +## 2.虚函数(virtual)可以是内联函数(inline)吗? + +- 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。 +- 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。 +- `inline virtual` 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 `Base::who()`),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。 + +```c++ +#include +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; +} +``` + + + diff --git a/inline/inline_virtual.cpp b/inline/inline_virtual.cpp new file mode 100644 index 0000000..1ec1a06 --- /dev/null +++ b/inline/inline_virtual.cpp @@ -0,0 +1,35 @@ +#include +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; +} diff --git a/inline/iv b/inline/iv new file mode 100755 index 0000000..e0b0e95 Binary files /dev/null and b/inline/iv differ diff --git a/virtual/README.md b/virtual/README.md index 176f74d..58d0f3d 100644 --- a/virtual/README.md +++ b/virtual/README.md @@ -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 diff --git a/virtual/set3/inline_virtual.cpp b/virtual/set3/inline_virtual.cpp new file mode 100644 index 0000000..1ec1a06 --- /dev/null +++ b/virtual/set3/inline_virtual.cpp @@ -0,0 +1,35 @@ +#include +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; +} diff --git a/volatile/README.md b/volatile/README.md new file mode 100644 index 0000000..f4dbcde --- /dev/null +++ b/volatile/README.md @@ -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) + diff --git a/volatile/noopt_vola.cpp b/volatile/noopt_vola.cpp new file mode 100644 index 0000000..58ceae6 --- /dev/null +++ b/volatile/noopt_vola.cpp @@ -0,0 +1,15 @@ +/* Compile code without optimization option */ +#include +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; +} diff --git a/volatile/nv b/volatile/nv new file mode 100755 index 0000000..b5eb220 Binary files /dev/null and b/volatile/nv differ diff --git a/volatile/volatile.cpp b/volatile/volatile.cpp new file mode 100644 index 0000000..fcf6115 --- /dev/null +++ b/volatile/volatile.cpp @@ -0,0 +1,16 @@ +/* Compile code with optimization option */ +#include + +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; +}