C++说爱你不容易-3
C++软件与嵌入式软件面经解析大全(蒋豆芽的秋招打怪之旅)
本章讲解点
- 1.1 C++与C的区别——看看你的理解是否深刻
- 1.2 从代码到可执行文件的过程
- 1.3 extern "C"
- 1.4 宏——到底是什么
- 1.5 内联函数
- 1.6 条件编译
- 1.7 字节对齐详解
- 1.8 Const——今天必须把它搞懂
- 1.9 Static作用
- 1.10 volatile和mutable
- 1.11 volatile在嵌入式里的应用
- 1.12 原子操作
- 1.13 指针与引用的区别
- 1.14 右值引用
- 1.15 面向对象的编程思想
- 1.16 类
- 1.17 类的成员
- 1.18 友元函数
- 1.19 初始化列表
- 1.20 this指针
- 1.21 继承
- 1.22 多态
- 1.23 虚函数与重写
- 1.24 虚构造函数与虚析构函数
- 1.25 函数重载
- 1.26 操作符重载
- 1.27 迭代器与指针
- 1.28 模板
- 1.29 C++智能指针
- 1.30 四种cast转换
- 1.31 Lambda
- 1.32 function和bind
受众:本教程适合于C/C++已经入门的学生或人士,有一定的编程基础。
本教程适合于互联网、嵌入式软件求职的学生或人士。
故事背景
蒋 豆 芽:小名豆芽,芳龄十八,蜀中人氏。卑微小硕一枚,科研领域苟延残喘,研究的是如何炒好一盘豆芽。与大多数人一样,学习道路永无止境,间歇性踌躇满志,持续性混吃等死。会点编程,对了,是面对对象的那种。不知不觉研二到找工作的时候了,同时还在忙论文,豆芽都秃了,不过豆芽也没头发啊。
隔壁老李:大名老李,蒋豆芽的好朋友,技术高手,代码女神。给了蒋豆芽不少的人生指导意见。
导 师:蒋豆芽的老板,研究的课题是每天对豆芽嘘寒问暖。
故事引入
蒋 豆 芽:(疑惑)老李,你怎么一直在我身边晃啊。
隔壁老李:(嘻嘻)没什么。你在睡午觉啊,那你好好睡。
蒋 豆 芽:老李,你快说,你神神秘秘的,我也睡不好了。
隔壁老李:豆芽,我们上一次的内容其实没有讲完,我们接着讲。
蒋 豆 芽:(晕)我就知道,害,谁叫我在找工作呢?哪里配拥有午睡?
1.10 volatile和mutable
隔壁老李:没事啦,豆芽,加油!先讲一个面试常问的问题——volatile和mutable。
mutable是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中,甚至结构体变量或者类对象为const,其mutable成员也可以被修改。mutable在类中只能够修饰非静态数据成员。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器每次会从内存里重新读取这个变量的值,而不是从寄存器里读取。特别是多线程编程中,变量的值在内存中可能已经被修改,而编译器优化优先从寄存器里读值,读取的并不是最新值。这就是volatile的作用了。
隔壁老李:volatile和mutable知识点比较简单,豆芽你记下来没有?这就是标准答案啊。还不赶紧请我吃饭?
蒋 豆 芽:好好好,老李,一定请你吃大餐。(请你吃个鬼哦)
1.11 volatile在嵌入式的应用
隔壁老李:不要着急,我们重点介绍下volatile在嵌入式的应用。
Volatile主要有三个应用场景:
(1)外围设备的特殊功能寄存器。
(2)在中断服务函数中修改全局变量。
(3)在多线程中修改全局变量。
隔壁老李:(1)外围设备的特殊功能寄存器。在嵌入式偏硬件方面的程序,我们经常要控制一些外围硬件设备,就拿I/O端口来说,我们会去操作映射到对应IO端口的寄存器。假设某一个寄存器的地址为0x1234
,在C语言中,我们可以定义一个指针pRegister
指向这个地址:
unsigned int *pRegister = (unsigned int *)0x1234;
在实际运用中(例如uart、ADC等等),我们经常会去判断一个寄存器中的值(或者寄存器中某一位)为‘0’还是‘1’。例如下面程序:
unsigned int *pRegister = (unsigned int *)0x1234; //wait while(*pRegister == 0){ //不改变*pRegister的值 } //Code...
我们的代码目的是不断的判断*pRegister
的值是否为‘0’。如果*pRegister
的值(值由硬件改变)在中途变为‘1’,则跳出死循环。
因为上面的循环中,*pRegister
的值并没有发生改变,因为我们的编译器会对上述代码进行优化,如下:
unsigned int *pRegister = (unsigned int *)0x1234; //wait if (*pRegister == 0){ while(1){ //不改变*pRegister的值 } } //Code...
经过优化后,在上面的循环中,*pRegister
的值不会发生改变,所以循环中就不再判断*pRegister
的值了,运行效率提升。但是pRegister
指向的特殊功能寄存器,其值是由硬件改变的,而软件却不再判断*pRegister
的值了,那么就进入死循环了,即使*pRegister
的值发生了改变,软件也察觉不到了。我们来查看下编译结果(反汇编):
30: b 30
可以看到,编译器好心办坏事,经过优化,缺少了cmp
指令,软件不再判断*pRegister
的值。这样自然将出现bug
那么改进就是加上volatile关键字。
volatile unsigned int *pRegister = (unsigned int *)0x1234;
再次查看编译结果:
28: ldr r3, [r2, #564] 2c: cmp r3, #0 30: bne 28
可以看到,加上volatile关键字后,编译器不再优化,有了cmp
指令,软件持续判断*pRegister
的值,当*pRegister
的值发生了改变,软件自然就能及时作出反应。
蒋 豆 芽:(恍然大悟)原来是这样。
隔壁老李:(嘿嘿)(2)在中断服务函数中修改全局变量也是容易出问题的。我们来看一个例子:
static int flag = 1; void main(void){ while (flag == 1){ //code ... } //code ... } void do_interrupt(void){ //中断服务程序 //code... flag = 0; }
上面的代码简单,只要flag的值为‘1’,就会一直运行循环里面的程序。刚才我们已经讲了,因为flag值在循环里没有改变,编译器就将对其优化。如下:
static int flag = 1; void main(void){ if (flag == 1){ while (1){ //code ... } } //code ... } void do_interrupt(void){ //code... flag = 0; }
我们来查看下编译结果(反汇编):
10: eafffffe b 10
发现问题了吗?同样少了cmp指令,而当发生中断时,flag值发生改变,但main函数中却察觉不到flag的改变,就陷入了死循环,明白了吧,改进同理,加上volatile关键字。
volatile static int flag = 1;
隔壁老李:(3)我们再来看看在多线程中修改全局变量。如下:
int cnt; void task1(void){ cnt = 0; while (cnt == 0) { sleep(1); } } void task2(void){ cnt++; sleep(10); }
豆芽,这段代码会出现什么问题应该不用我多解释了吧,同理。解决办法依然是加上volatile关键字。
蒋 豆 芽:(晕晕沉沉)嗯嗯。。。嗯
隔壁老李:(敲脑袋)醒醒,豆芽,我们还要接着讲。
1.12 原子操作
隔壁老李:好了,volatile关键字的应用场景我们就讲清楚了。诶,豆芽,说到多线程,这里可以使用volatile关键字,其实还有一种解决方法,那就是原子操作。
原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。
蒋 豆 芽:感觉这很类似互斥锁啊。
隔壁老李:没错,有点类似,但是原子操作比锁效率更高,这是因为原子操作更加接近底层,它的实现原理是基于总线加锁和缓存加锁的方式。因为作为延伸的知识点,它的实现原理我们就不再详细讲解了,豆芽你有兴趣的话可以自己去学习一下。
我们着重看看原子操作的应用,我们以一个例子来说明:
#include <atomic> #include <iostream> #include <time.h> #include <thread> #include <vector> using namespace std; // 全局的结果数据 long total = 0; void func() { for (int i = 0; i < 100000; ++i) { // 对全局数据进行无锁访问 ++total; } } int main() { // 计时开始 clock_t start = clock(); // 创建100个线程 vector<thread *> vec(100); for (int i = 0; i < 100; ++i) { vec[i] = new thread(func); } for (int i = 0; i < 100; ++i) { vec[i]->join(); } // 计时结束 clock_t finish = clock(); // 输出结果 cout << "result:" << total << endl; cout << "duration:" << finish - start << "ms" << endl; for (int i = 0; i < 100
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
<p> - 本专刊适合于C/C++已经入门的学生或人士,有一定的编程基础。 - 本专刊适合于互联网C++软件开发、嵌入式软件求职的学生或人士。 - 本专刊囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构等一系列知识点的讲解,并且最后总结出了高频面试考点(附有答案)共近400道,知识点讲解全面。不仅如此,教程还讲解了简历制作、笔试面试准备、面试技巧等内容。 </p> <p> <br /> </p>