嵌入式知识点学习篇五(C\C++)
变量/函数
- 全局变量和静态变量的区别是什么?
- 全局变量可不可以定义在可被多个.c文件包含的头文件中?为什么?
- 局部变量能否和全局变量重名?
- 为什么析构函数必须是虚函数?
- 为什么C++默认的析构函数不是虚函数?
- C++中析构函数的作用?
- 静态函数和虚函数的区别?
- 重载和覆盖有什么区别?
- 虚函数表具体是怎样实现运行时多态的?
- C语言是怎么进行函数调用?
- 请你说一说select
- 请你说说fork,wait,exec函数
全局变量和静态变量的区别是什么?
- 全局变量的作用域为程序块,而局部变量的作用域为当前函数。
- 内存存储方式不同,全局变量(静态全局变量,静态局部变量)分配在全局数据区(静态存储空间),后者分配在栈区。
- 生命周期不同。全局变量随主程序创建而创建,随主程序销毁而销毁,局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在了。
- 使用方式不同。通过声明为全局变量,程序的各个部分都可以用到,而局部变量只能在局部使用。
全局变量可不可以定义在可被多个.c文件包含的头文件中?为什么?
可以,在不同的C文件中以static形式来声明同名全局变量。
可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错。
局部变量能否和全局变量重名?
能,局部会屏蔽全局。
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。
对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。
为什么析构函数必须是虚函数?
将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
为什么C++默认的析构函数不是虚函数?
C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。
C++中析构函数的作用?
如果构造函数打开了一个文件,最后不需要使用时文件就要被关闭。析构函数允许类自动完成类似清理工作,不必调用其他成员函数。
析构函数也是特殊的类成员函数。简单来说,析构函数与构造函数的作用正好相反,它用来完成对象被删除前的一些清理工作,也就是专门的扫尾工作。
静态函数和虚函数的区别?
静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。
重载和覆盖有什么区别?
- 覆盖是子类和父类之间的关系,垂直关系;重载同一个类之间方法之间的关系,是水平关系。
- 覆盖只能由一个方法或者只能由一对方法产生关系;重载是多个方法之间的关系。
- 覆盖是根据对象类型(对象对应存储空间类型)来决定的;而重载关系是根据调用的实参表和形参表来选择方法体的。
虚函数表具体是怎样实现运行时多态的?
原理:
虚函数表是一个类的虚函数的地址表,每个对象在创建时,都会有一个指针指向该类虚函数表,每一个类的虚函数表,按照函数声明的顺序,会将函数地址存在虚函数表中,当子类对象重写父类的虚函数的时候,父类的虚函数表中对应的位置会被子类的虚函数地址覆盖。
作用:
在用父类的指针调用子类对象成员函数时,虚函数表会指明要调用的具体函数是哪个。
C语言是怎么进行函数调用?
大多数CPU上的程序实现使用栈来支持函数调用操作,栈被用来传递函数参数、存储返回信息、临时保存寄存器原有的值以备恢复以及用来存储局部变量。
函数调用操作所使用的栈部分叫做栈帧结构,每个函数调用都有属于自己的栈帧结构,栈帧结构由两个指针指定,帧指针(指向起始),栈指针(指向栈顶),函数对大多数数据的访问都是基于帧指针。下面是结构图:
栈指针和帧指针一般都有专门的寄存器,通常使用ebp寄存器作为帧指针,使用esp寄存器做栈指针。
帧指针指向栈帧结构的头,存放着上一个栈帧的头部地址,栈指针指向栈顶。
请你说一说select
- select函数原型
intselect(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,structtimeval*timeout);- 文件描述符的数量
单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量;(在linux内核头文件中定义:#define __FD_SETSIZE 1024)。 - 就绪fd采用轮询的方式扫描
select返回的是int,可以理解为返回的是ready(准备好的)一个或者多个文件描述符,应用程序需要遍历整个文件描述符数组才能发现哪些fd句柄发生了事件,由于select采用轮询的方式扫描文件描述符(不知道那个文件描述符读写数据,所以需要把所有的fd都遍历),文件描述符数量越多,性能越差。 - 内核 /用户空间内存拷贝
select每次都会改变内核中的句柄数据结构集(fd集合),因而每次调用select都需要从用户空间向内核空间复制所有的句柄数据结构(fd集合),产生巨大的开销。 - select的触发方式
select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次调用select还是会将这些文件描述符通知进程。 - 优点
a. select的可移植性较好,可以跨平台;
b. select可设置的监听时间timeout精度更好,可精确到微秒,而poll为毫秒。 - 缺点
a. select支持的文件描述符数量上限为1024,不能根据用户需求进行更改;
b. select每次调用时都要将文件描述符集合从用户态拷贝到内核态,开销较大;
c. select返回的就绪文件描述符集合,需要用户循环遍历所监听的所有文件描述符是否在该集合中,当监听描述符数量很大时效率较低。
请你说说fork,wait,exec函数
父进程产生子进程使用fork拷贝出来一个父进程的副本,此时只拷贝了父进程的页表,两个进程都读同一块内存,当有进程写的时候使用写实拷贝机制分配内存,exec函数可以加载一个elf文件去替换父进程,从此父进程和子进程就可以运行不同的程序了。fork从父进程返回子进程的pid,从子进程返回0,调用了wait的父进程将会发生阻塞,直到有子进程状态改变,执行成功返回0,错误返回-1。exec执行成功则子进程从新的程序开始运行,无返回值,执行失败返回-1。