嵌入式软件工程师面试遇到的经典题目

Posted 行稳方能走远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式软件工程师面试遇到的经典题目相关的知识,希望对你有一定的参考价值。

摘自:嵌入式软件工程师面试遇到的经典题目
作者:一只青木呀
发布时间: 2020-11-04 23:43:16
网址:https://blog.csdn.net/weixin_45309916/article/details/109499825

嵌入式软件工程师面试遇到的经典题目

1、找错误

char * s1="hello";
char * s2="world";
char * s3=strcat(s1,s2);

   
  • 1
  • 2
  • 3

这样做对吗,如果不对请说明原因。

解答:
不对,s1与s2都为常量指针,其内容不可修改,运行就会产生段错误。

下面的代码有何问题?

void test1()
{
 char string[10];
 char* str1 = "0123456789";
 strcpy( string, str1 );
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

字符串strl的末尾是以’ \\0 ’结尾的,所以他的长度是11,而string的长度不够。。。。

但是我自己写了这样的程序,他是可以拷贝的。。。。

2、下面的代码输出是什么,为什么?

void foo(void)
{
	unsigned int a=6;
	int b=-20;
	(a+b>6)?puts(">6"):puts("<6")
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

输出“>6”,应为无符号数和有符号相加,有符号的整形数会转化成一个无符号的整型数,而且负的有符号整型数转换后会变得非常大,所以相加会大于6。

3、C语言编译时动态链接和静态链接得区别是什么?

动态库:
1、链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序可以共用,节省内存。
2、程序升级简单,因为app里面没有库的源代码,升级之后只要库的名字不变,函数名以及参数不变,只是实现做了优化,就能加载成功。
3、. 加载速度比静态库慢
4、发布程序需要提供依赖的动态库

静态库:
1、静态库被打包到程序中加载速度快
2 、发布程序无需提供静态库,应为已经在app中,移植方便
3 、 链接时完整的拷贝至可执行文件中,被多次使用就会有多次冗余拷贝
4、 更新,部署,发布麻烦

4、C语言关键字static得作用是什么?

①隐藏作用, 可以在不同的文件中定义同名变量和同名函数。
②对于变量来说, 保持变量持久, 静态数据区的变量会在程序刚刚运行时就完成
初始化, 也是唯一一次初始化; 储存在静态数据区, 静态存储区只有两种变量(全
局变量和 static 静态变量) 。
③默认初始化为 0x00,和全局变量一样的属性, 减少程序员的工作量。

5、分别说明一下三个变量声明得含义:

 int const *p;

int * const p;

int const *p const;

 
  • 1
  • 2
  • 3
  • 4
  • 5

int const*p=const int *p; const修饰的是指针 p,表示指针p的值不能改变,而p(即地址)是可以改变的;

int * const p; const修饰的是p(即地址)是常量,不可改变,但是*p的值可以改变。

int const *p const ; 上面两种情况兼得,表示只读,其地址以及地址中的值都不可改变

6、简述TCP/IP镞包含哪些分段,每一层有哪些常用协议?

应用层:http dns telnet ftp TFTP 。。。。
传输层:tcp udp
网络层:ip ICMP ARP rarp
数据链路层:ethnet ethnet2 802.3 ppp fr x.25 hdlc
物理层:比特流

7、从在浏览器地址栏中输入www.baidu.com到看到百度首页,这个过程中间经历了什么?都涉及到哪些网络协议?

按照时间顺序:
1.客户端浏览器获取用户在地址栏输入的域名。
2.客户端浏览器将域名发送给DNS域名系统,请求解析。
3.DNS解析域名得到相应的IP,返回给客户端浏览器。
4.客户端浏览器根据IP向服务器发起TCP三次握手,建立TCP连接。
5.客户端浏览器向服务器发送HTTP请求,请求百度首页。
6.服务器通过HTTP响应向客户端浏览器返回百度首页文件。
7.释放TCP连接。
8.客户端浏览器解析html文件,根据文件内容获取CSS、JS等资源文件,将页面渲染展示给用户。

TCP/IP五层模型中网络层及以上用到的协议:
1.应用层:HTTP、DNS、HTTPS
2.传输层:TCP、UDP
3.网络层:IP、ARP

8、编写strcat函数

2.strcat函数原型

char *my_strcat(char *dest,const char *src) //将源字符串加const,表明其为输入参数
{
  char *strDest=dest;
  assert(dest!=NULL && src!=NULL);      //对源地址和目的地址加非0断言
  //若使用while(*Dest++),则会出错,指向'\\0'之后,会出现dest++,则指向了个'\\0'的下一个位置,
  while(*dest !='\\0')
  {
    dest++;                                            //循环体内的++可以使指向字符串结束标志'\\0'
  }
  while((*dest++=*src++)!='\\0');
  return strDest; 
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

之后会问为什么要char *的返回值:
主要是为了实现链式表达式。
如 strcpy(buf, strcat(dest, src) );

9、使用C语言中的#define来定义一个常量来表示一年有多少秒?

#define SECONDS_PER_YEAR (606024*365)UL

一定要加括号,宏定义只是替换,不加括号会出错。。。。

10、实现把字符串转化成整数

int my_atoi(char *str)
{
	int sum=0,status=1;
	if(str == NULL)
	{
		return 0;
	}
	if(*str == '-')
	{
		status = -1;
		str++;
	}
	while((*str)!='\\0')
	{
		sum=sum*10+ ((*str)-'0');
		str++;
	}
	return sum*status;
	
}


 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

11、写一个程序验证系统的大小端存储格式

/*方法1*/
typedef union {
	int i;
	char c;
}my_union;
 
int checkSystem1(void)
{
	my_union u;
	u.i = 1;
	return (u.i == u.c);
}
/*方法2*/
int checkSystem2(void)
{
	int i = 0x12345678;
	char *c = &i;
	return ((c[0] == 0x78) && (c[1] == 0x56) && (c[2] == 0x34) && (c[3] == 0x12));
}


 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

12、如何判断一个byte数据中有多少bit为1?

int select(unsigned char data)
{
	int count=0,i=1;
	while(data!=0)
	{
		count +=  (data & i);
		data >>= 1;
	}
	return count;	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

13、 C语言中关键字volatile的含义

volatile 的意思是“易失的,易改变的”。这个限定词的含义是向编译器指明变量的内容可能会由于其他程序的修改而变化。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份

一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

2、多任务环境下各任务间共享的标志应该加volatile;

3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

一个参数既可以是const还可以是volatile吗?解释为什么。
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

14、进程间通信的方式有哪些?

1.无名管道( pipe ): 管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

2.有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

3.消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

4.信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

5.信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

6.共享内存( shared memory ) : 共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

7.套接字( socket ) : 套解字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

15、堆和栈的区别

①、申请方式不同:栈由系统自动分配,堆是由人为自行开辟(malloc,new)
② 、申请的大小不同:栈是从高地址像低地址分配的,分配空间较小,堆是由地址向高地分配的,空间较大
③ 、申请效率不同:栈由系统分配,分配速度较快,堆一般较慢
④ 、栈是连续的地址空间,堆不是连续的地址空间,很容易产生内存碎片,浪费内存。

16、分别给出bool,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)

	Bool: if(!var)
	Int : if(var==0)
	Float:  const float val=0.00000001
			If((var >= -val) && (var <= val))
  • 1
  • 2
  • 3
  • 4
  • 5

17、 进程和线程的区别

进程和线程的根本区别是进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。另外区别还有资源开销、包含关系、内存分配、影响关系、执行过程等。

  • 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

  • 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

  • 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

  • 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

  • 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

进程和线程的根本区别是进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

18.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。

#define MIN(A, B) ((A) <= (B)? (A) : (B))
这个测试是为下面的目的而设的:
(1)标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符 变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为 了能达到要求的性能,嵌入代码经常是必须的方法。
(2)三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if -then-else更优化的代码,了解这个用法是很重要的。
(3)懂得在宏中小心地把参数用括号括起来。
(4)我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
宏定义#define MIN(A, B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:
((*p++) <= (b) ? (*p++) : (b))这个表达式会产生副作用,指针p会作两次++自增操作。

19.预处理器标识#error的目的是什么?

这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
编译程序时,只要遇到 #error 就会跳出一个编译错误当程序比较大时,往往有些宏定义是在外部指定的(如makefile),或是在系统头文件中指定的,当你不太确定当前是否定义了 XXX 时,就可以改成如下这样进行编译:
#ifdef XXX
#error “XXX has been defined”
#else

#endif
这样,如果编译时出现错误,输出了XXX has been defined,表明宏XXX已经被定义了。

20. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

这个问题用几个解决方案。我首选的方案是:

while(1) 
{ 
} 

 
  • 1
  • 2
  • 3

一些程序员更喜欢如下方案:

for(;;) 
{ 
} 

 
  • 1
  • 2
  • 3

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto

Loop: 
... 
goto Loop; 

 
  • 1
  • 2
  • 3

应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

21. 用变量a给出下面的定义

(1)一个整型数(An integer): int a;
(2)一个指向整型数的指针(A pointer to an integer): int *a;
(3)一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an
integer): int **a;
(4)一个有10个整型数的数组(An array of 10 integers) :int a[10];
(5)一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to
integers): int *a[10];
(6)一个指向有10个整型数数组的指针(数组指针):int (*a)[10];int *p[n](指针数组)
(7)指向函数的指针,该函数有一个整型参数并返回一个整型数(函数指针):int (*a)(int);
(8)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整
型数( An array of ten pointers to functions that take an integer argument and return an
integer ): int (*a[10])(int)。
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。 但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么做准备呢?

22. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码 ,第一个设置a的bit 3,第二个清除a的bit 3。在以上两个操作中,要保持其它位不变。

对这个问题有三种基本的反应
(1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
(2)用bit fields。bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间
是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙沾实际硬件的边。
(3)用#define和bit masks操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:

#define BIT3 (0x1 < <3) 
static int a; 
void set_bit3(void) 
{ 
a |= BIT3; 
} 
void clear_bit3(void) 
{ 
a &= ~BIT3; 
} 

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的我希望看到几个要点:说明常数、|=和&=~操作。

23. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa55。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr; 
ptr = (int *)0x67a9; 
*ptr = 0xaa55; 

 
  • 1
  • 2
  • 3

一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55; 

 
  • 1

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

24. C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;
c = a+++b;
这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修改性。

25. 整型数组作为参数传递时,无法在子函数中获得其长度!只有字符串可以,因为它有一个尾巴标识(‘\\0’)!所以,整型的数组长度,必须与数组名一同传递到子函数才可以!

以上是关于嵌入式软件工程师面试遇到的经典题目的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式面试经典30问

嵌入式面试经典30问

嵌入式面试经典30问

嵌入式软件工程师笔试面试指南-数据结构与算法

strstr函数实现(嵌入式软件面试题页1)

软件测试工程师经典面试题汇总