嵌入式面试总结

最近面了很多公司,总结一下最近遇到的题目,很多高频出现,后来看了一下,这些都是很经典的题目,写一个笔记,日后复习,不定期更新

宏定义问题

1、写一个标准宏MIN,输入两个参数返回较小的一个

个人理解:

      这个题考的事使用宏定义函数的基础,外加三目运算符的考察

我的解答:

    #define MIN(a,b)((a)>=(b)?(b):(a))

2、使用宏定义 定义一个常数,一年有多少秒(忽略闰年)

个人理解:

    这个题考察宏定义基本用法,还有普通平台下数据类型长度的问题在32位的设备上int是 4个byte 8位和16位是2个byte,为了兼容各平台最好使用unsigned long,以便在使用的时候不会出问题

 

我的解答:
    #define SECOND_YEAR   (60*60*24*360ul) 

(ps. 有一种写法是

#define SECOND_YEAR (60*60*24*360)ul

,这种我没有验证,但是再16位或者8位的系统上这个数值已经大于0xffff,可能出现超出的情况,保守起见用上面的方法 )

3、宏定义#和##的区别

个人理解:

    我觉得这个可能会很多人不知道后面那个,至少我是这样的,根本没有使用过##,不过在apm的源码中看到过很多次,之前没注意,现在重新温习一下

我的解答:

 

#define a_printf(x) printf( "token " #x" = %d\n ", token##x ) 
所以a_printf(1);就是相当于 printf("token 1 = %d\n",token1);

还有一个#的例子很经典

 

#define MONCK(ARGTERM) /
    printf("The term " #ARGTERM " is a string/n")
MONCK(A to B);
输出:
The term A to B is a string

4、#error、#warning有什么用?

个人理解:

    这个可能非常简单,但是很少人用,在apm源码中非常常用,因为apm要跨五六个平台,编译的时候要做出提示或者是报错

我的解答:

#define NIU_BI
#ifdef NIU_BI
#error "there is a niubi problem"
#else
#warning "ni niubi,mei mao bing"
#endif

预编译的时候会进行一个处理,如果定义了NIU_BI,那么编译器将会报错,输出后面的字符串,如果没有的话,编译器将会输出一个警告,但并不影响编译

5、如何使用可变参数的宏

 

个人理解:

熟悉Java的同学对这个再熟悉不过了,有点类似。确实可变参数宏在嵌入式编程中是一大利器

网络解答:

可变参数可以用三个点(...)来表示,,可以用__VA_ARGS__来展开,如:

#define err(...) fprintf(stderr,__VA_ARGS__)
err("%s %d/n","The error code: ",48);

一般,我们可以这样打印错误信息:

 

fprintf(stderr,"%s %d/n","The error code ",48);

但想要另外输出其它信息,我们可以这样定义一个可变参数的宏,例如:

#define errout(a,b,...) /
    fprintf(stderr,"File %s     Line %d/n",a,b); /
    fprintf(stderr,__VA_ARGS__)

那么我们就可以这样使用这个宏:

errout(__FILE__,__LINE__,"Unexpected termination/n");

 

因为__VA_ARGS__在fprintf中的内部实现使用了逗号来作分割符,所以必须要求至少有一个参数,如果想使__VA_ARGS__为空,可以这样使用:

fprintf(stderr, ##__VA_ARGS__);

6、为什么要使用宏定义,有哪些注意事项?

因为 宏的效率高,不需要压栈 和出栈,而且可以节省代码
使用宏不能使用递归,宏只是进行简单替换,
定义的时候最好加上括号,确定优先级,反之在预编译的时候出现替换
造成优先级变化的问题。C语言里没有模板,而c语言的宏恰恰相当于C++的模板,
虽然宏很好用,但是还是不要过于使用宏,最好只是用于一些函数比较难实现的场景。

谷歌宏定义写法标准   需要翻墙
谷歌宏定义写法标准   需要翻墙

15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

 

#define dPS struct s *
typedef struct s * tPS;

      以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

 

dPS p1,p2;
tPS p3,p4;

第一个扩展为

 

struct s * p1, p2;

 

上面的代码定义p1为一个指向结构的指针,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。

指针问题

1、用变量a给出下面的定义(我觉得这个题是必考题,虽然我俩次都特么答错了)

a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and 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); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int a); // An array of 10 pointers to functions that take an integer argument and return an integer

2、char * const p、 char const * p 、const char *p 上述三个有什么区别?

个人理解:

    这个我觉得是个必考题,但是却基本没有问的

我的解答:

    第一个const加在p头上,常量指针,所以内容不变,指针的地址可以变,第二个const指向指针,所以指针地址不能变,但内容可以变,第三个指向整体,所以指针和内容都不变

 

char * const p; //常量指针,p的值不可以修改
char const * p;//指向常量的指针,指向的常量值不可以改
const char *p; //和char const *p一样
const char *p; //*p是const,p可变:const 后面紧跟的是char,所以*p是一个char字符,不可变  
  
const (char *) p;//p是const,*p可变:const 后面紧跟的是(char *)这个整体,所以p是char*类型,不可变。  
  
char* const p; //p是const,*p可变:const 后面紧跟的是p,所以p不可变  
  
const char* const p; //p和*p都是const:第一个const后面紧跟的是char,所以char类型的字符*p不可变;第二个const后面紧跟的是p,所以p不可变。  
  
char const * p;// *p是const,p可变:const后面紧跟的是*,但是单独的*不能表明修饰的内容,所以将*p看成一个整体,所以const修饰的是*p,*p不可变。  
  
(char*) const p;//p是const,*p可变:const紧跟的是p,所以p不可变。  
  
char* const p;// p是const,*p可变:const紧跟的是p,所以p不可变。  
  
char const* const p;// p和*p都是const:第一个const紧跟的是*,不能表明修饰的内容,将后面整体的(* const p)
看成一个整体,那就说明*p不可变,第二个const后面紧跟的是p,所以p不可变。  

3、下面程序的输出结果是?

char str1[] = "abc"; 
char str2[] = "abc"; 
const char str3[] = "abc";
const char str4[] = "abc"; 
const char *str5 = "abc";
const char *str6 = "abc"; 
char *str7 = "abc"; 
char *str8 = "abc"; 
cout << ( str1 == str2 ) << endl; 
cout << ( str3 == str4 ) << endl; 
cout << ( str5 == str6 ) << endl; 
cout << ( str7 == str8 ) << endl;

结果是:0 0 1 1。 str1,str2,str3,str4是数组变量,它们有各自的内存空间;而str5,str6,str7,str8是指针,它们指向相同的常量区域。

机器平台差异

1、一个32位的机器,该机器的指针是多少位?

指针是多少位只要看地址总线的位数就行了。80386以后的机子都是32的数据总线。所以指针的位数就是4个字节了。

 

栈区、堆区、全局区、文字常量区、程序代码区

 

1、下面程序有什么错?

char* s="AAA"; 
printf("%s",s); 
s[0]='B';
printf("%s",s);
答案:"AAA"是字符串常量。s是指针,指向这个字符串常量,所以声明s的时候就有问题,这种声明编译器并不能发现错误。 所以定义的时候要加const,即const char*s="AAA"; 然后又因为是常量,所以对是s[0]的赋值操作是不合法的。

2、栈、堆、队列、段的区别是什么

五大区这篇文章有详解,单片机这篇。

数组

1、下面程序输出结果是?

main() 
{ 
    int a[5]={1,2,3,4,5}; 
    int *ptr=(int*)(&a+1); 
    printf("%d,%d",*(a+1),*(ptr-1)); 
} 
答案:2。5 *(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5 &a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5个int) int *ptr=(int*)(&a+1); 则ptr实际是&(a[5]),也就是a+5 原因如下: &a是数组指针,其类型为 int (*)[5]; 而指针加1要根据指针类型加上一定的值,不同类型的指针+1之后增加的大小不同 a是长度为5的int数组指针,所以要加 5*sizeof(int) 所以ptr实际是a[5] 但是prt与(&a+1)类型是不一样的(这点很重要) 所以prt-1只会减去sizeof(int*) a,&a的地址是一样的,但意思不一样,a是数组首地址,也就是a[0]的地址,&a是对象(数组)首地址,a+1是数组下一元素的地址,即a[1],&a+1是下一个对象的地址,即a[5].

2、多维数组取值问题

内存问题

 

 

1、请问以下代码有什么问题:

int main()
{
    char a;
    char *str=&a;
    strcpy(str,"hello");
    printf(str);
    return 0;
} 

答案:没有为str分配内存空间,将会发生异常问题出在将一个字符串复制进一个字符变量指针所指地址。虽然可以正确输出结果,但因为越界进行内在读写而导致程序崩溃。

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

个人理解:

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

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

  A more obscure approach is:
一个较晦涩的方法是:

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

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案,实际上我的品位就是第二种,这就是我面试老是失败的原因,哈哈哈哈,眼高手低。

 

3.请问以下代码有什么问题:

#include 
#include 
void getmemory(char *p)
{
p=(char *)malloc(100);
strcpy(p,"helloworld");
}
int main( )
{
char *str=NULL;
getmemory(str);
printf("%s/n",str);
free(str);
return 0;
答案:程序崩溃,getmemory中的malloc 不能返回动态内存, free()对str操作很危险

4. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?

      内存碎片,碎片收集的问题,变量的持行时间等等。

延伸:下面的代码片段的输出是什么,为什么?

char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
    puts("Got a null pointer");
else
    puts("Got a valid pointer");

该代码的输出是"Got a valid pointer"。不知道为什么

嵌入式系统的几种无限循环方式

 

1、while(1){}

这种每个人都会,但是在实际生产中机会没有人用,至少我没看见过

2、for(;;)

这种相当常用了,嵌入式系统基本上都是这种

3、loop:           goto loop;

这种我最喜欢了,哈哈,刚学C语言的时候我就用这个方法不让黑框关掉,别人都用getchar(),到最后上visual studio的使用优势体现出来了。

关键字问题

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

网上有个答案是:定义静态变量,我估计这个你答完基本算完蛋,这是个人就能答出来

比较合理的答案有:

1) 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
4)一个类里面的静态函数,可以不创建对象就可以直接调用
前三条来自网络, 据说第三条能够体现本地化数据和代码范围的好处和重要性。

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

答案:表示常量不可以修改的变量。
const意味着"只读"。尽管这个答案不是完全的答案。

 延伸(同上面提到的const)

const int a;
int const a;
const int *a;
int * const a;
int const * a const;
      前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。

const的好处

1) 关键字const的作用是为给读你代码的人传达非常有用的信息,声明一个参数为常量是为了告诉了用户这个参数的应用目的。
2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

3、关键字volatile有什么含意?并举出三个不同的例子?

答案:提示编译器对象的值可能在编译器未监测到的情况下改变。

    一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子(我特么当时答成防止出现意向不到的值,面试官楞了):

1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)(这个真的是很常用)
3) 多线程应用中被几个任务共享的变量

延伸

1) 一个参数既可以是const还可以是volatile吗?解释为什么。

1) 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2) 一个指针可以是volatile 吗?解释为什么。

2) 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

3) 下面的函数有什么错误:

int square(volatile int *ptr)
{
        return *ptr * *ptr;
}

3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)
{
    int a,b;
    a = *ptr;
    b = *ptr;
    return a * b;
}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
    int a;
    a = *ptr;
    return a * a;
}

C和C++的区别

1.c和c++中的struct有什么不同?

答案:c和c++中struct的主要区别是c中的struct不可以含有成员函数,而c++中的struct可以。c++中struct和class的主要区别在于默认的存取权限不同,struct默认为public,而class默认为private

操作系统

 

1.列举几种进程的同步机制,并比较其优缺点。

答案: 原子操作 信号量机制 自旋锁 管程,会合,分布式系统

2,mutex 和binary semaphore有什么区别

这篇文章

 

数据结构

1,如何找到一个循环链表的头指针?

    这个题还真不明白

2、如何把链表a->b->c变成c->b->a

3、双向链表

 

中断(Interrupts)

1. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准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应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。

第三和第四是加分项。

延伸:可重入

可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

C语言开发原则

整数自动转换原则

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

个人理解:

    整数自动转换原则,我个人之前是完全不知道的。

    这无符号整型问题的答案是输出是 ">6"。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。

延伸

分为显式转换和隐式转换,强转为显式转换。

    隐式转换规则:
       1、字符必须先转换为整数(C语言规定字符类型数据和整型数据之间可以通用) 。
       2、short型转换为int型(同属于整型) 。
       3、float型数据在运算时一律转换为双精度(double)型,以提高运算精度(同属于实型) 。
    其次,有下面的规则。

    当不同类型的数据进行操作时,应当首先将其转换成相同的数据类型,然后进行操作,转换规则是由低级向高级转换。转换规则如下图所示:

 

 

 

  • 23
    点赞
  • 222
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值