嵌入式面试题

首先给大家分享一个在线练习面试题的网站:牛客网。

该网站包含使用实例、应用技巧、基本知识点总结和需要注意事项,具有参考价值,需要的朋友可以参考一下

嵌入式面试题-点击进行练习

第一部分:基本概念

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§计算得到的是一个指针变量的字节数,一般为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]))

更多面试资料分享

  • 6
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式基地

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值