原创 嵌入式面试题,不断更新

2021-1-19 22:47 374 3 2 分类: MCU/ 嵌入式
原文:
https://zhuanlan.zhihu.com/p/342410727

第一部分:基本概念
1.关键字static的作用
(1)在函数体内:一个被声明为静态的变量,在这一函数被调用过程中,维持其值不变。(该变量值初始化一次)
(2)在模块内,函数体外:一个被声明为静态的变量,可以被模块内所用函数访问,但不能被模块外其他函数访问。
(3)在模块内,一个被声明为静态的函数,该函数只能被这一模块内其他函数调用,其他模块无法调用。
2.引用与指针的区别:
(1)引用必须初始化,指针不用
(2)引用初始化后不能修改,指针可以改变所指对象
(3)不存在指向空的引用,指针存在空值
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。
引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
关-注-公-众-号:嵌入式基地
回-复:【面试】
获-取-面-试-资-料
3..h头文件中的#ifndef/#define/#endif作用:
防止头文件被重复引用
4.#include <>与#include “”区别:
<>是从系统指定的路径下寻找;””是先从当前路径下寻找
5.描述实时系统的基本特性:
在特定时间内完成特定的任务,实时性与可靠性。
6.全局变量与局部变量是否存在区别?
全局变量存在静态数据区,局部变量存在栈中
7.堆栈溢出一般是由于什么原因?
(1)没有回收垃圾资源
(2)层次太深的递归调用
8.冒泡排序算法的时间复杂度
O(n^2)
9.什么函数不能声明为虚函数?
构造函数
10.队列与栈的区别:
队列:先进先出;栈:先进后出
11.不能做switch()的参数类型
实型
12.局部变量是否能和全局变量重名?
可以。局部变量会屏蔽全局变量。若要使用全局变量,使用::。
在函数内引用该重名变量时,会使用同名的局部变量,而不会使用全局变量(就近原则)。
对一些编译器而言,同一函数内可以定义多个同名的局部变量,例如:在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在该循环内。
13.如果引用一个已经定义过的全局变量?
可以包含其头文件,也可使用关键字extern。
引用头文件:通过引用头文件的方式来引用某个头文件中声明的变量,假如将该变量写错了,在编译阶段会报错。
Extern:通过extern的方式引用,若将变量写错,编译阶段编译器不会报错,在链接阶段会进行报错提示。
14.全局变量能不能定义在可被多个.c文件包含的头文件中?
可以。在不同的C文件中以static的形式来声明同名全局变量。
可以在不同的C文件中声明同名的全局变量,前提是只能在一个C文件中对变量赋初值,此时链接不会报错。
15.for(;1;)有什么问题?什么意思?
与while(1)相同,无限循环
16.do…while()与while…do有什么区别?
前者循环一遍后再判断;后者先判断后循环
17.static全局变量、局部变量、函数与普通全局变量、局部变量、函数有什么区别?
(1)static全局变量与非static全局变量区别:
存储方式上相同,都是静态存储。
作用域:非static全局变量的作用域在各个文件中都是有效的;static的全局变量作用域只限于当前源文件中,只初始化一次。
(2)static函数与普通函数作用域不同。Static函数只能在当前源文件中使用,而非Static函数可以在其他源文件中使用。
(3)static局部变量只初始化一次,下次调用使用上次的数值。
18.程序的内存分配
C/C++编译的程序占用的内存分为以下几部分。
(1) 栈区,编译器自动分配释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。
(2) 堆区,由程序员分配释放。若程序员没有进行资源回收,程序结束时,可能会由OS回收。与数据结构中的堆是两回事。
(3) 全局区(静态区),全局变量与静态变量是存储在一起的。初始化的全局变量与初始化的静态变量在一块区域,未初始化的全局变量与未初始化的静态变量存储在一起。程序结束后由OS释放
(4) 常量区,存储常量,字符串。程序结束后由OS回收。
(5) 程序代码区,存放函数体的二进制代码。
示例:
int a = 0; //全局初始化区域
char *p1; //全局未初始化区域
int main(int argc, char const *argv[]) {
 int b; //栈
 char s = "sss"; //栈
 char *p2; //栈
 char *p3 = "123456"; //p3在栈上,“123456\0”在常量区
 static int c = 0; //全局初始化区域
 p1 = (char *)malloc(10); //分配得到的区域在堆上
 p2 = (char *)malloc(20);
 strcpy(p1, "123456"); //“123456\0”在常量区
 return 0;
}
19.堆和栈的区别
(1)申请方式:
堆:由程序员申请,在c语言中使用malloc函数申请。(嵌入式单片机应用中不建议使用此函数)。
例如:p2 = (char *)malloc(10),p2本身是在栈上。
C++中,new运算符
栈:由系统分配。
(2)申请后系统响应:
栈:若当前栈的剩余空间大于申请空间,系统将会给程序提供内存;否则,将提示异常,栈溢出。
堆:操作系统有个记录空闲内存地址的链表,当系统收到程序的申请时,将会遍历该链表,寻找第一个内存空间大于所申请空间的堆节点,然后将该节点从链表中删除,并将该内存空间分配给程序。找到的堆节点的大小不一定正好等于申请的大小,此时系统会将多余的部分重新放入空闲链表中。对于大多数系统,这块内存空间的首地址会记录本次分配的大小,这样,代码中的delete语句才能正确释放该块内存空间。
(3)申请大小的限制:
栈:在windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域(栈顶地址与容量是系统提前规定好的)。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。堆的大小限制于系统中有效的虚拟内存。
(4)申请效率:
栈:由系统自动分配,速度快,但程序员无法操作。
堆:由new、malloc分配,速度慢,容易产生内存碎片,但使用方便。
Windows下最好使用virtual alloc分配内存,这块内存既不在堆,也不在栈,是直接在进程的地址空间中保留一块内存,速度快,灵活,使用不方便。
(5)堆和栈中的存储内容
栈:在函数调用时,是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数C语言编译器中,参数是由右往左入栈的,然后是函数中的局部变量。静态变量不入栈,在全局初始化区域。
当函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续执行。
堆:一般在堆的头部用一个字节存储堆的大小。堆中的具体内容由程序员分配。
(6)存取效率比较:
Char s1[] = “ssssss”;
Char *s2 = “bbbbbb”;
aaaaaa是在运行时赋值的,bbbbbb是在编译时确定的。
但在以后的存取中,栈上的数组比指针指向的字符串(堆)块。
 void main() {
 char a = 1;
 char c[] = "1234567890";
 char *p = "1234567890";
 a = c[1];
 a = p[1];
 return;
}
对应的汇编代码
10:a=c[1]; 
004010678A4DF1movcl,byteptr[ebp-0Fh] 
0040106A884DFCmovbyteptr[ebp-4],cl 
11:a=p[1];
0040106D8B55ECmovedx,dwordptr[ebp-14h] 
004010708A4201moval,byteptr[edx+1] 
004010738845FCmovbyteptr[ebp-4],al
第一种:在读取时,直接将字符串中的元素读到寄存器c1中
第二种:先将指针读取到edx中,再根据edx读取字符。
20.什么是预编译,什么时候需要预编译?
预编译又称为预处理,是做代码文本的替换工作,处理#开头的指令,比如拷贝#include包含的头文件代码;#define宏定义的替换。在程序开始编译之前进行。
C语言编译系统在对程序编译之前,先进行预处理。预处理主要提供以下功能:
(1)宏定义(2)头文件包含(3)条件编译
21.关键字const含义
const 只读
应用:
const int a; //常整型数
int const a; //常整型数
const int *a; //a是一个指向常整型数的指针(指针可以修改,整型数不可修改)
int * const a; //a是一个指向整型数的常指针(指针不可以修改,整型数可以修改)
const int * const a; //a是一个指向常整形数的常指针(都不可修改)
使用const关键字的理由:
(1) 为读代码的人提供非常有用的信息,实际上应用一个参数为常量是为了告诉用户这个参数的应用目的。
(2) 通过给优化器一些附加信息,使用关键字const也许能够产生更紧凑的代码
(3) 合理使用const可以使编译器很自然的保护那些不希望被修改的参数,防止其被无意的代码修改。
22.关键字volatile含义?应用例子:
含义:
一个被定义为volatile的变量是说这个变量可能会被意想不到的改变,这样编译器就不会去假设这个变量的值了。准确的说,优化器在用到这个变量时,必须每次都小心点重新读取这个变量的值,而不是直接使用寄存器中的备份值。
应用:
(1)并行设备的硬件寄存器
(2)一个中断服务子程序中会访问到的非自动变量。
(3)多线程应用中被几个任务共享的变量
其他问题:
1. 一个参数既可以是const还可以是volatile吗?为什么?
2. 一个指针可以是volatile吗
3. 下面函数有什么错误
Int square(volatile int *ptr) {
Return *ptr * *ptr;
}
(1) 可以,一个例子为只读的状态寄存器。它是volatile是应为他可能被意想不到的改变。它是const是应为程序不因该去修改它。
(2) 是的,虽然不常见。一个例子为中断服务子程序修改一个指向一个buffer的指针时。
(3) 这段代码的目的是为了返回指针*ptr的平方,但是由于*ptr是一个指向volatile类型的参数,因此编译器将产生类似于下面的代码:
int square(volatile int *ptr) {
  int a, b;
  A = *ptr;
  B = *ptr;
  Return a * b;
}
由于*ptr的值可能被意想不到的改变,因此,a与b的值可能不同,结果这段代码返回的结果与预期可能并不相同。
正确代码:
int square(volatile int *ptr) {
  int a;
  A = *ptr;
  Return a * a;
}
23.三种基本数据模型
按照数据结构类型的不同,将该数据模型划分为层次模型、网状模型、关系模型
24.结构体与联合体有什么区别
共用体,允许在相同的内存地址存储不同的数据类型;可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同内存地址的方式
共用体,占用的内存应该足够存储共用体中最大的成员。
结构体,占用内存是内部所有变量所占空间之和。结构体,每个成员变量都有自己的内存地址。
25.#define与const区别:
1.const有数据类型,宏定义没有数据类型;编译器可以对前者进行安全检查,对于后者不能进行安全检查,只能进行字符替换。
2.有些调试工具可以对const进行调试,而宏定义无法调试。
3.const定义的常量有作用域,而#define不重视作用域,默认定义处到文件结尾。
26.数组与指针区别:
数组要么在静态数据区被创建,要么在栈上被创建。
指针可以随时指向任意类型的内存块。
(1)修改内容上的差别:
char a[] = “hello”;
A[0] = ‘x’;
Char *p = “world”;//p指向的是常量字符串
P[0] = ‘x’;//编译器无法发现该错误,运行时会报错
(2)使用sizeof()运算符计算容量:
数组可以使用sizeof计算出容量,而sizeof(p)计算得到的是一个指针变量的字节数,一般为4个字节,而不是p所指向的内存容量。
注意:当数组作为函数形参进行传递时,该数组自动退化为同类型指针:
void TestBufferSize1(char a[]) {
    printf("buffer size = %d \r\n", sizeof(a));
}
void TestBufferSize2(char a[6]) {
    printf("buffer size = %d \r\n", sizeof(a));
}
int main(){
    char b[6] = "12345";
    printf("b size = %d \r\n", sizeof(b));
    TestBufferSize1(b);
    TestBufferSize2(b);
    system("pause");
}
27.分别写出bool、int、float、指针类型的变量a与零比较的语句:
(1)bool:
If(!a) or if(a)
(2)int
If(a == 0)
(4)float:
Const EXPRESSION EXP = 0.000001
If(a < EXP && a >-EXP)
(5)指针:
If(a != NULL) or if(a == NULL)
28.如何判断一段程序是由c编译还是由c++编译程序编译的?
#ifdef __cplusplus
    cout << "c++";
#else
    printf("c");
#endif
29.讨论含参数的宏与函数优缺点:
带参宏函数
处理时间编译时程序运行时
参数类型没有参数类型问题定义实参,形参类型
处理过程不分配内存分配内存
程序长度变长不变
运行速度不占用运行时间调用和返回占用运行时间

30.使用两个栈来实现一个队列的功能
设两个栈A,B,并将其初始化为空
入队:
将新元素push入栈A;
出队:
(1) 判断栈B是否为空
(2) 若不为空,则将栈A中的所有元素依次pop出,并push到栈B
(3) 将栈B的栈顶元素pop出
这样的实现,入队与出队的平摊复杂度都为O(1)
31.位操作
给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a的bit 3。在以上操作中,保持其它位不变。
(1) 通过宏定义,以及bit masks操作。这种方法可移植性高
#define BIT3 (0x01 << 3)
static int a;
void SetBit3(void) {
    a |= BIT3;
}
void ClearBit3(void) {
    a &= ~BIT3;
}
(2) 使用bit fields。Bit fields是被扔到C语言死角的方式。这种方法保证了你的代码在不同编译器之间是不可移植的,同时也保证代码是不可重用的。
32.访问固定的内存位置
嵌入式编程中,经常会去访问某个特定内存位置的数据。在某个工程中,需要设置一个绝对地址为0x56a3的整型变量的值为0x3344。编译器是一个纯粹的ANSI编译器。
(1) 方法一:
int *ptr = NULL;
ptr = (int *)0x56a3;
*ptr = 0x3344;
(2) 方法二:
*(int * const)(0x56a3) = 0x3344;
推荐使用第一种。
33.中断
中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供了一种扩展—让标准C支持中断。
具体代表的事实是,产生了一个新的关键字__interrupt。
下面这段代码使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评价这段代码。
__interrupt double compute_area(double radius) {
 double area = PI * radius * radius;
 printf("\nArea = %f", area);
 return area;
}
(1) ISR不能返回一个值
(2) ISR不能传递参数
(3) 在许多编译器中,浮点数一般是不可重入的。并且,ISR应该是短而有效的,在ISR中做浮点数运算是不明智的。
(4) Printf()经常有重入与性能上的问题
34.typedef
Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事情。
例:
#define dPS struct s *
Typedef struct s *tPS;
以上两种情况都是要定义dPS和tPS作为一个指向结构s的指针。哪种方法更好?
答:typedef更好,原因请看下面示例。
dPS s1,s2;
tPS s3,s4;
第一个展开为struct s *s1,s2;此时,s1指向的是结构的指针,而s2是一个实际的结构。
35.写一个“标准”的宏
(1)交换两个参数的宏定义
#define SWAP(a, b)\
        (a) = (a) + (b);\
        (b) = (a) - (b);\
        (a) = (a) - (b);\
(2)输入两个参数,输出较小的参数
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
(3)1年中有多少秒?(忽略闰年)
#define SECONDS_OF_YEAR (60 * 60 * 24 * 365)UL
(4)已知一个数组table,使用宏定义求出数组元素的个数
#define TABLE_SIZE (sizeof(table) / siezof(table[0]))

作者: 嵌入式基地, 来源:面包板社区

链接: https://mbb.eet-china.com/blog/uid-me-3946381.html

版权声明:本文为博主原创,未经本人允许,禁止转载!

文章评论0条评论)

登录后参与讨论
广告
我要评论
0
3
1
2
3
4
5
6
7
8
9
0
广告
关闭 热点推荐上一条 /8 下一条