1:堆和栈的区别
①栈的空间由系统自动分配或释放,堆的空间手动分配或释放
②栈的空间有限,堆上有很大的自由存储空间,malloc分配的空间在堆上,使用完后需要释放
2:static关键词的作用
①修饰局部变量,将其生命周期改为与全局变量相同,作用域不变
②修饰全局变量时, 当一个源程序由多个源文件组成时,将其作用域从整个源程序改为该源文件
③修饰函数时,表明该函数作用域为该源文件内
3:volatile关键字的作用
①与const关键词对立,表示“可变的”,修饰变量时表明该变量需要从内存单元中读取,不能直接从寄存器中拷贝
②常用于并行设备的硬件寄存器,中断服务子程序访问到的变量,多线程之间被几个任务共享变量
3.1:const关键词的作用
- 定义变量为常量
- 修饰函数的参数,表示在函数体中不能改变这个参数的值
- 修饰函数的返回值,表示该返回值不可变
- 修改常指针
4:extern关键字
①用于声明一个已在其他文件定义的变量或函数
②在c++程序中调用c编译后的函数,要加extern c的声明;因为C++要支持函数重载,而c不支持,函数被C++编译后在库中的名字与C语言的不同,C++提供 extern C来解决名字匹配问题。
5:实现strcpy函数
char *strcpy(char *strDest, const char *strSrc)
{
if (!(strDest && strSrc))
return NULL;
char *ret = strDest;
while ((*strDest++ = *strSrc++)!='\0');
return ret;
}
6:引用和指针的区别
(1). 指针是一个变量,存储的是一个地址,引用,是原变量的别名
(2). 指针所指向的内存空间在程序运行过程中可以改变,而引用所绑定的对象一旦绑定就不能改变。(是否可变)
(3). 指针在内存中占有存储内存空间,引用相当于变量的别名,在内存中会占4字节内存
(4). 引用没有 const,指针有 const,const 的指针不可变;
(5). 指针可以为空,但是引用必须绑定对象。(是否可为空)
(6). 指针可以有多级,但是引用只能一级。(是否能为多级)
引用在创建的时候必须初始化,只有在调用虚函数时,才能实现动态绑定
7:malloc的用法和注意点
该函数用于分配堆上的n个字节,并返回指向这块内存的指针,如果分配失败则返回NULL;
注意:①申请了内存空间之后,必须检查是否申请成功
②当不再使用申请的内存,必须进行释放,且释放后必须将指针置NULL,否则会导致野指针
③malloc和free配套使用,申请不释放为内存泄漏,释放多次,会导致错误
④malloc的类型是void*,使用时需要将其强制转换为需要的类型指针
8:C和C++的区别
①c面向过程,完成一件事情,按照步骤一步步完成;c++面对对象,将不同的食物看成一个对象,依靠多个对象之间的相互联系、相互操作,完成一定的功能。
②c++输入输出流不同
③c++有命名空间namespace
④c++有缺省函数,对每个函数的参数有一个初值
⑤c++函数重载,相同的函数,不同的函数列表
⑥c++有引用,c是指针,详情见标题6(引用和指针的区别)
⑦动态内存分配不同
⑧c++智能指针
⑨c++有stl,vector list等
⑩struct在c++可相当于class使用,唯一的区别是struct默认修饰词是public,calss默认修饰词是private
⑪const修饰的变量,在c中不能用于数组的下标,c++可以
9:C语言的编译过程
预处理->编译->汇编->链接,
GCC编译器的几个组成部分:预处理(cpp)、编译器(ccl)、汇编器(as)、链接器(ld)
①预处理,将所有的#include头文件以及宏定义替换成其真正的内容,gcc的预处理是预处理器cpp来完成的,得到的是文本文件。
②编译,将经过预处理之后的程序转换成特定汇编代码,得到的是.s文件
③汇编,将上一步的汇编代码转换成机器码,得到的是目标文件,是二进制格式,这一步会为每一个源文件生成一个目标文件。
④链接,将多个目标文件以及所需的库文件(.so等)链接成最终的可执行文件exe。
10.重载、重写、隐藏的区别
重载:是指同一可访问区内被声明几个具有不同参数列(参数的类型、个数、顺序)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
隐藏:是指派生类的函数屏蔽了与其同名的基类函数,主要只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。
重写(覆盖):是指派生类中存在重新定义的函数。函数名、参数列表、返回值类型都必须同基类中被重写的函数一致,只有函数体不同。
重写和重载的区别:
范围区别:对于类中函数的重载或者重写而言,重载发生在同一个类的内部,重写发生在不同的类之间(子类和父类之间)。
参数区别:重载的函数需要与原函数有相同的函数名、不同的参数列表,不关注函数的返回值类型;重写的函数的函数名、参数列表和返回值类型都需要和原函数相同,父类中被重写的函数需要有 virtual 修饰。
virtual 关键字:重载的函数可以有也可没有,重写的函数基类中必须有 virtual关键字的修饰,
隐藏和重写,重载的区别:
范围区别:隐藏与重载范围不同,隐藏发生在不同类中。
参数区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定相同;当参数不同时,无论基类中的函数是否被 virtual 修饰,基类函数都是被隐藏,而不是重写。
11:智能指针
答:智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露(利用自动调用类的析构函数来释放内存)。它的一种通用实现技术是使用引用计数(除此之外还有资源独占,如(auto_ptr),只引用,不计数(weak_ptr))。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
12:内存泄漏
内存泄漏:申请了一块内存空间,使用完毕后未及时释放//由于疏忽或错误导致的程序未能释放已经不再使用的内存,造成了内存的浪费。一般是:调用了malloc/new等内存申请的操作,但缺少了对应的free/delete,
防止:
(1)计数法:使用new或者malloc时,让该数+1,delete或free时,该数-1,程序执行完打印这个计数,如果不为0则表示存在内存泄露
(2)一定要将基类的析构函数声明为虚函数
(3)良好的编码习惯,有new就有delete,有malloc就有free,保证它们一定成对出现
(4)智能指针:智能指针是 C++ 中已经对内存泄漏封装好了一个工具,可以直接拿来使用。
后果:
只发生一次小的内存泄漏可能不被注意,但泄漏大量内存的程序将会出现各种证照:性能下降到内存逐渐用完,导致另一个程序失败;
13:深拷贝和浅拷贝
(1)浅拷贝是增加了一个指针,指向原来已经存在的内存。
(2)深拷贝是增加了一个指针,并新开辟了一块空间让指针指向这块新开辟的空间。浅拷贝在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误。
14:回调函数
如果把函数的指针作为参数传递给一个函数,当这个指针被用来调用其所指向的函数时,就是回调函数。简单的说:把一段可执行的代码像参传递那样传给其他代码,而这段代码会在某个时刻被调用执行,就叫做回调。如果代码立即被执行称为同步回调,如果在之后晚点的某个时间再执行,称为异步回调。
回调函数的本质是对函数指针的应用
使用回调函数,和普通函数调用区别:
1)在主入口程序中,把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,且不需要修改库函数的实现,变的很灵活,这就是解耦。
2)主函数和回调函数是在同一层的,而库函数在另外一层。如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数了,这也就是在日常工作中常见的情况。
二、linux相关知识
1:进程和线程的区别
①进程是系统资源分配的最小单位,线程是程序执行的最小单位,一个进程可以有多个线程,一个线程只属于一个进程。
②系统开销和资源共享:进程拥有自己独立的地址空间,每次启动一个进程系统会为它分配数据段、代码段、堆栈段,而线程与进程共享相同资源,仅仅拥有自己的局部变量(栈)和寄存器,共享全局变量、堆和文件等公共资源,因此cpu开销线程明显小于进程
③消息通信:线程之间的通信很方便,但要注意互斥和同步,进程间通信用到IPC,主要有FIFO,socket,管道pipe,信号量
④健壮性:多进程程序不容易崩溃,多线程程序会因为某一线程掉线而崩溃
2:进程间通信
目的:
数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
1:管道:
一个进程通过调用管程的一个过程进入管程。在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。
(1)无名管道(内存文件):是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用。进程的亲缘关系通常是指父子进程关系。
(2)有名管道(FIFO文件,借助文件系统):也是半双工的通信方式,但是允许在没有亲缘关系的进程之间使用,先进先出的通信方式。
2:共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。
3:消息队列:消息队列是有消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号:传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4:套接字socket:适用于不同机器间进程通信,在本地也可作为两个进程通信的方式。
5:信号:用于通知接收进程某个事件已经发生,比如按下ctrl + C就是信号。
6:信号量:是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,实 现进程、线程的对临界区的同步及互斥访问。
3:线程间通信
线程间的同步方式包括互斥锁、信号量、条件锁、读写锁
①互斥锁,保证在任何时刻,都只有一个线程访问该资源,当获取锁操作失败时,线程进入阻塞,等待锁释放。
②读写锁,分为读锁和写锁,处于读操作时,可以运行多个线程同时读,但写操作同一时刻只有一个线程获得写锁。
**互斥锁和读写锁的区别:
(a)读写锁区分读锁和写锁,互斥锁不区分;
(b)互斥锁同一时刻允许一个线程访问,无论读写;读写锁同一时刻只允许一个线程写,但可以多个线程读。
③自旋锁:在任何时刻只能有一个线程访问资源,但获取锁操作失败时,不会进行睡眠,而是原地自旋,直到锁被释放。优点:节省了线程从睡眠到被唤醒的时间消耗,提高效率。
④条件锁:某一线程因为某个条件未满足时可以使用条件变量使该程序处于阻塞状态,一旦条件满足,即可唤醒该线程(常与互斥锁配合使用)
⑤信号量:计数器,允许多个线程同时访问同一个资源。
4:死锁
如果一组进程中的每个进程都在等待一个事件,而这个事件只能由改组的另一个进程触发,这种情况会导致死锁。简单理解为:死锁就是两个线程同时占用两个资源,但又在彼此等待对方释放锁。产生条件:
①互斥条件:
②不剥夺条件:
③请求和保持条件:
④循环等待条件:
5:嵌入式编程,大小端问题
①大端:低位字节存放在高地址,高位字节存放在低地址
②小端:高位字节存放在高地址,低位字节存放在低地址
STM32是小端模式,从低字节->高字节开始存放,而大端模式是从高字节->低字节开始存放
6:如何对绝对地址赋值
*(unsigned int*)0x1000000 = 1234;
如何将程序跳转到指定地址执行
typedef void (*)() voidfunc;
*((voidfunc)0x1000000)();
另外一种实现:*((void(*)()0x1000000)();
11:嵌入式硬件协议
I2C,SPI,GPIO,
12:自旋锁和互斥锁,读写锁
自旋锁可以在中断中使用,互斥锁会引起阻塞
13:系统调用有哪些,执行过程是怎样的?
14:大小端问题,0x12345678在小端模式存储方式是?
78 56 34 12
15:信号量是用来干嘛的?
用来同步和互斥,如果初始值为0,则用于同步,如果初始值非0,则用于互斥
互斥是死锁的必要条件之一
为什么要字节对齐