嵌入式软件面试题总结

时间:2021-02-06 14:41:13

参考http://blog.csdn.net/qq_21792169/article/details/50210713

以及Bruce.yang大神的博客   http://blog.csdn.net/morixinguan

一、预处理指令

1、C语言预编译指令的作用是什么?有哪些预编译指令,作用分别是什么?

预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理

C语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符
预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。

下面是部分预处理指令:

        指令             用途
         #           
空指令,无任何效果
         #include    
包含一个源代码文件
         #define     
定义宏
         #undef      
取消已定义的宏
         #if         
如果给定条件为真,则编译下面代码
         #ifdef      
如果宏已经定义,则编译下面代码
         #ifndef     
如果宏没有定义,则编译下面代码

         #elif       
如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
         #endif      
结束一个#if……#else条件编译块
         #error      
停止编译并显示错误信息

2、用预处理指令#define 声明一个常数,用以表明1年中有多少秒。

答:   #defineSECONDS_PER_YEAR  (60 * 60 * 24 * 365)UL

考点:

(1)#define 语法的基本知识(不能以分号结束,括号的使用)
(2)预处理器将计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,这样更清晰明了。
(3)这个表达式将使一个16位机的整型数溢出,因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
(4)表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要,这说明考虑的很全面。

3、写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。

答:#define MIN(A,B) ((A) <= (B) ? (A) : (B)) 

考点:
(1)标识#define在宏中应用的基本知识。这是很重要的。因为在  嵌入(inline)操作符 变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
(2)三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
(3)懂得在宏中把参数用括号括起来。

(4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
        least = MIN(*p++, b);

((*p++) <= (b) ? (*p++) : (b)) 这个表达式会产生副作用,指针p会作两次++自增操作。

4、预处理器标识#error的目的是什么?

停止编译并显示错误信息。

二、数据声明及关键字

1、用变量a给出下面的定义

a) 一个整型数(An integer) 
b)一个指向整型数的指针( A pointer to an integer) 
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer toan intege)r 
d)一个有10个整型数的数组( An array of 10integers) 
e) 一个有10个指针的数组,该指针是指向一个整型数的。(Anarray of 10 pointers to integers) 
f) 一个指向有10个整型数数组的指针( A pointerto an array of 10 integers) 
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a functionthat takes an integer as an argument and returns an integer) 
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argumentand return an integer )
答案是: 

a) int a; // An integer 
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argumentand returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take aninteger argument and return an integer

2、关键字static的作用是什么?

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

3、关键字const有什么含意?

一个变量不允许被改变,产生静态作用。使用const在一定程度上可以提高程序的安全性和可靠性。const推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

4、关键字volatile有什么含意?

volatile是一个类型修饰符type specifier),就像大家更熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。简单地说就是防止编译器对代码进行优化。比如如下程序:

XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;

对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,则编译器会逐一的进行编译并产生相应的机器代码(产生四条代码)

三、操作

1、位操作

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

用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:

#define BIT3(0x1 << 3)
static int a;

voidset_bit3(void)
{
a |= BIT3;
}
voidclear_bit3(void)
{
a &= ~BIT3;
}

2、访问指定地址的内存

嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

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

一个较晦涩的方法是:

    *(int * const)(0x67a9) =0xaa55;
建议你在面试时使用第一种方案。

3、中断(Interrupts)

中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interruptdouble compute_area (double radius) 
{
double area = PI * radius * radius;
printf("/nArea = %f", area);
return area;
}

这个函数有太多的错误了:
1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

__interrupt用来声明一个函数是中断处理函数;在严格的ANSIC/C++模式下,也可以使用interrupt关键字来代替。中断处理函数要遵循特殊的寄存器保存规则和退出顺序,从而保证代码的安全。在C/C++程序中产生中断时,所有被中断子程序使用,或者被中断子程序调用的函数使用的状态都需要被保留。此外,__interrupt定义的函数不能有参数,也没有返回值,即:

__interrupt void int_handler()
{
unsigned int flags;
...
}

4、运算中的类型转换

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

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

这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ">6"。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。

参考Bruce.yang大神的博客   http://blog.csdn.net/morixinguan

5、 分离16进制的高低位

#include<stdio.h>
#include<stdlib.h>
intmain(void)
{
unsigned int temp = 0x10 ;
unsigned int high = (temp - temp % 0x10) / 0x10 ;
unsigned int low = temp % 0x10 ;
printf("high:%u low:%u\n",high , low) ;
system("PAUSE");
return 0;
}

6、 分离unsigned int的高低位

#include<stdio.h>
#include<stdlib.h>
intmain(void)
{
unsigned int offset = 0x1234 ;
unsigned int high = 0 ;
unsigned int low = 0 ;
high = ((offset >> 8 )&0xff);
low = (offset&0xff) ;
printf("high = %p->%d low = %p->%d\n",high , high ,low,low);
system("PAUSE");
return 0;
}

7、 一个字节16进制组合成2字节16进制

#include<stdio.h>
#include<stdlib.h>
intRecordBuffer[10];
intbuffer_write(unsigned int *buffer , int size_to_write) ;
intmain(void)
{
unsigned int buffer[] ={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
buffer_write(buffer , 10);
int i ;
for(i = 0 ; i < 10 ; i++)
printf("Recordbuffer[%d]:%p\n",i , RecordBuffer[i]);
system("PAUSE");
return 0;
}

intbuffer_write(unsigned int *buffer , int size_to_write)
{
int *p = (int *)buffer ;
int i ;
for(i = 0 ; i < size_to_write ;i +=2)
{
RecordBuffer[i/2] = *(p+i)|(*(p+i+1)<< 8) ;

}
}

8static关键字作用

(1) static修饰的局部变量它的数值是上一次函数调用结束之后的数值,在修饰变量的时候,static修饰的静态局部变量只执行一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。 

(2)静态局部变量在定义的时候没有初始化,默认的初始值为0。

(3)static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。
(4)static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0; 
#include <stdio.h>
#include <stdlib.h>
int RecordBuffer[10];
int buffer_write(unsigned int *buffer , int size_to_write) ;
void fun();
int main()
{
fun();
fun();
system("PAUSE");
return 0;
}
void fun()
{
static int a = 0;
a++ ;
printf("a:%d\n",a);
}

输出1、2

9、 无符号数转换成ascii

#include<stdio.h>
#include<stdlib.h>
#definetoascii(c) (((unsigned char)(c))&0x7f) //因为0x7f代表127,ascii码从0开始到127结束
intmain(void)
{
int ret = 0 ;
ret = toascii('a'); //小a的ascii
printf("a的ascii码:%d\n",ret);
ret = toascii('A'); //大a的ascii
printf("A的ascii码:%d\n",ret);
ret = toascii('z'); //小z的ascii
printf("z的ascii码:%d\n",ret);
ret = toascii('Z'); //大Z的ascii
printf("Z的ascii码:%d\n",ret);
system("PAUSE");
return 0;
}
嵌入式软件面试题总结

10、 16进制数字高低位数据交换

#include<stdio.h>
#include<stdlib.h>
#definetoascii(c) (((unsigned char)(c))&0x7f) //因为0x7f代表127,ascii码从0x7f开始到0xff结束
//system("PAUSE");
//将一个8位数高低4位交换
static inlineunsigned char bswap_8(unsigned char v)
{
return ((v & 0xff) << 4) | (v >> 4) ;
//将参数(v & 0xff)<< 4 相当于放到高位, v>> 4 位相当于放在低位
} //以下两个代码分析雷同
//将一个16位数高低8位交换
static inlineunsigned short bswap_16(unsigned short v)
{
return ((v & 0xff) << 8) | (v>> 8);
}
//将一个32位数高低16位交换
static inlineunsigned int bswap_32(unsigned int v)
{
return ((v & 0xff) << 24) | ((v& 0xff00) << 8) |
((v & 0xff0000) >> 8) | (v>> 24);
}
intmain(void)
{

unsigned short v = 0x1000 ;
printf("1、\n原来的v:%d\n",v);
printf("16位数高低8位转化后的v:%d(0x%x)=========>0x%x--->%d\n",v,v,bswap_16(v),bswap_16(v)) ;
unsigned char a = 0x0a ;
printf("\n2、\n原来的a:%d\n",a);
printf("8位数高低4位转化后的a:%d(0x%x)==========>0x%x--->%d\n",a,a,bswap_8(a),bswap_8(a)) ;
unsigned int b = 0x00001111;
printf("\n3、\n原来的b:%d\n",b);
printf("32位数高低16位转化后的b:%d(0x%x)========>0x%x--->%d\n",b,b,bswap_32(b),bswap_32(b)) ;
system("PAUSE");
return 0 ;
}
嵌入式软件面试题总结

11、 字符串操作

#include<stdio.h>
#include<stdlib.h>
#definetoascii(c) (((unsigned char)(c))&0x7f) //因为0x7f代表127,ascii码从0x7f开始到0xff结束
//system("PAUSE");
#include<string.h>
#define ucharunsigned char
#defineuint unsigned int
//删除字符串中所有的数字
voiddel_str_Num(char* str)
{
static int i , j;
while(str[i]!='\0')
{
if(str[i]>'9'||str[i]<'0')
str[j++]=str[i];
i++;
}
str[j]='\0';
}
//删除字符串中的第一个字符
static char*ltrim(char *s, char c)
{
while(*s!=0&&*s==c)
s++;
return s;
}

//查找并删除字符串中指定的任意字符
static char*delstr_c(char s[100] , char c)
{
char *p , *q ;
for(p = s , q = s ; *p != '\0' ; p++)
if(*p != c)*q++ = *p ;
*q = *p ;
return s ;
}


/*将字符串s中出现的字符c删除*/
voiddel_str_c(char s[],int c)
{
inti,j;
for (i = 0, j = 0; s[i] != '\0'; i++)
{
if (s[i] != c)
{
s[j++] = s[i];
}
}
s[j] = '\0';
}
//查找字符串中的字符并将对应的字符删除
char*strdel_c(char *s,char c)
{
char *p=s,*q=s;
for(;*p;p++)
{
if(*p != c )
{
if(q == p)
q++ ;
else
*q++ = *p ;
}
}
*q = '\0';
return s;
}
//将字符串左右翻转
char*strfilp(char* str)
{
//h指向str的头部
char* h = str;
char* t = str;
char ch;

//t指向s的尾部
while(*t++) ;
t--; //与t++抵消
t--; //回跳过结束符'\0'

//当h和t未重合时,交换它们所指向的字符
while(h < t)
{
ch = *h;
*h++ = *t; //首尾移动
*t-- = ch;
}

return(str);
}

int main()
{
char str[20] = "23.3hhlo965";
char *str1 = "hello";
del_str_c(str , '.'); //删除该字符串中的.
printf("%s\n",str);
del_str_Num(str); //删除该字符串中的所有数字
printf("%s\n",str);
strfilp(str); //将字符串左右翻转
printf("%s\n",str);
system("PAUSE");
return 0;
}

嵌入式软件面试题总结

12、数据段、代码段

#include<stdio.h> 
#include<stdlib.h>
int BSS ; //位于BSS段,存放在程序组未初始化的内存区域
int data = 100; //位于数据段,存放在程序中已经初始化的内存区域
static int y ;//静态区
int stack(void);
intmain(void)
{
static int k ; //静态区
int i , j ; //栈区 内存自动申请自动释放
int *p = NULL ;
p = malloc(1024) ; //堆区 内存手动申请手动释放
free(p);
return 0 ;
}
intstack(void)
{
//栈区
int i ;
return 0 ;
}

13、将字符串中的数字返回为整数型

#include<stdio.h>
#include<stdlib.h>
//system("PAUSE");
typedef int U32;
U32 String2Dec(const char *pstr )
{
char ch;
U32 value;
value = 0;
//从字符串的第一个字符遍历到'\0'
while( *pstr != '\0' )
{
//获取字符
ch = *pstr++;
//判断字符是否在0-9这个范围
if( ch >= '0' && ch <='9' )
{
//ch-'0'相当于将字符转换为整数
value = value * 10 + ch - '0';
}
}
//返回
return value;
}
intmain(void)
{
char *pstr = "123a456" ;
int num = String2Dec(pstr);
printf("num:%d\n",num);
system("PAUSE");

}

嵌入式软件面试题总结


14、使用strstratoi的结合实现一个C语言获取文件中数据的工具

#include<stdio.h> 
#include<fcntl.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
intget_buf_data(char *buf,char *data)
{
char *p1 =NULL,* p2=NULL;
int num =0;
p1 = buf;
p2 = strstr(p1,data);
if(p2 == NULL)
{
printf("%s no find %s ---%s\r\n",__FUNCTION__ ,buf,data);
return 0;
}
p1 = p2+strlen(data);
num = atoi(p1);
return num;
}
intmain(void)
{
int fd = -1 ;
char buf[1024];
fd = open("abc.txt",O_RDWR);
if(-1 == fd)
{
printf("open fair!\n");
return -1 ;
}
memset(buf,0,sizeof(buf));
read(fd,buf,1024);
close(fd);
int num =get_buf_data(buf,"student_num:");
printf("num:%d\n",num);
system("PAUSE");
return 0 ;
}

嵌入式软件面试题总结嵌入式软件面试题总结

15、strlen与sizeof的区别

strlen:是计算字符串的长度,当遇到‘\0’时停止,不包括‘\0’。

sizeof:计算在内存中占得大小。要考虑字节对其等问题。32位系统,所有指针一般占4字节。

char *a[3][4]占48个字节内存空间。

数组作为参数时,数组名退化为指针变量。