总结下网上的嵌入式C语言面试《葵花宝典》

总结下网上的嵌入式C面试"葵花宝典"

1、用预处理指令“#define”声明一个常数,用来表示一年中有多少秒:

答案:#define SECOND (365*24*60*60)UL

详解:

  这个其实就是宏定义。(365*24*60*60),就是:(365天*24小时*60分*60秒),怎么样,够清楚了吧;再有就是后面的“UL”,后面加个“UL”的意思是,这个常数在存储器中要按照“无符号长整型”来存储,不过,我试过,貌似有些编译器要是在后面加上这个“UL”会报错,具体还是大家自己试验吧。

2、写一个标准宏"MIN",让这个宏可以输入两个参数,并且返回较小的一个。

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

详解:

  在这里用到了C语言中的唯一一个三目运算符“:”,意思就是:如果“A<B”这个条件成立,输出“A”,不成立则输出“B”,需要注意的是,我们需要老老实实的使用括号,万一你写的可复杂,造成各种运算符之间优先级混乱,最终会导致输出错误!

  另外,大家看下这个宏定义有什么问题:“MIN(*p++,b)”,如果将这个式子按上面定义的宏定义运算,会出现什么问题呢?

答案:如果按照上面的宏定义展开,会是这样子:“MIN(*p++,b)”会被替换为:“((*p++)<(b)?(*p++),b)”,看懂了吧!本来是,如果(*p++)<(b),那就把(*p++)的值输出,但是,实际上输出的却又是一个"*p++",这样,等于就++了两次,结果不会是你想要的。在此,我们得牢记了,宏定义就是纯粹的、硬生生的把你定义的东西展开,它不管你写的什么东西的。

3、预处理器标示“#error”的目的是什么?

  答案:看到这东西,大家都很熟悉,也都很烦它吧?是的,它就是总是出现的编译错误提示,其实我们自己也可以搞一些编译错误,看程序:

#include <stdio.h>

#define SEC 1993    //这里定义成1993

int main()
{
    #if(SEC!=1994)    //我们想要的条件是SEC==1994(但上面我写成了1993),如果不是1994,接下来下面的"#error"就有用了
    #error "SEC Not 1994"    //上面条件不满足,就输出这句错误提示
    #endif
    return 0;
}

上面程序在编译的时候,就会出错了,这个错误是我自己设定的,如下图:

怎么样,这种错误看见就烦吧,不过这个错误看见不会烦,因为这是我让它出错的,不出错才有问题了。。。

 4、数据声明:

其他论坛或者博客什么的地方也见过了,不打算总结前几个简单的,对后面几个相对难点的做下笔记:

e、一个有10个整型指针的数组;

f、一个指向有10个整型数数组的指针;

g、一个指向函数的指针,该函数有一个整型参数并返回一个整型数;

h、一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数;

答案:

e、int *a[10];

详解:“a”是一个数组,并且里面存放的是10个整型指针类型的数据;

f、int(*a)[10];

详解:“a”是指针,指向一个数组,另外,此数组是一个有10个int型元素的数组。

g、int (*a)(int);

详解:刚开始看到的时候,我也有些迷惑,经常见到的函数都是“int a(int x);”这种形式,但这里却是"int (*a)(int);"前面的“*a”还容易理解,这跟上面的数组指针一样嘛,但是后面却只有一个“(int)”了,刚看到的时候一直在想为什么这样。。。今天突然一想,C语言函数形参在声明的时候是可以只写一个类型名的,后面可以不写具体变量名(但是实际定义的时候就要写了),所以他这个是一个省略的写法:“int (*a)(int);”,我再写一个看起来没有那么绕的:int (*a)(int x);或者:int (*fun)(int i);这样就看清楚了。

h、int (*a[10])(int);

详解:

5、关键字“static”的作用是什么?

  我的理解:

  1、在子函数内,static修饰后,函数退出时,变量只初始化一次,而且不会释放,下次调用时,可以接着用;如:

 1 #include <stdio.h>
 2 
 3 void fun1()
 4 {
 5     static int i=0;
 6     printf("%d
",i);
 7     i=3;//函数退出前,将"i"=3,第二次调用时,“i”的值不会是1,而是这个3
 8 }
 9 
10 int main()
11 {
12     fun1();//第一次调用
13     fun1();//第二次调用
14     return 0;
15 }

输出结果如下:

  2、在一个文件内的变量加“static”修饰,则只在这个文件内有用,即使是全局的变量,仍然只在这个文件内有效;

6、关键字“const”有什么含义?

  详解:“const”意味着只读;

1 #include <stdio.h>
2 
3 const int i=0;
4 
5 int main()
6 {
7     i=1;//"i"被"const"修饰过后,变为只读,在这里重新复制,就会报错
8 }

 关于"const"的其它例子:

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

  前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型 数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数 是不可修改的,同时指针也是不可修改的)。

7、关键字“volatile”有什么作用?

  一个被定义为“volatile”的变量是说这个变量可能会被意想不到的改变,这样,编译器就不会去假设这个变量的值了。准确的说,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

8、访问特定内存:

  在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

代码:

1 #include <stdio.h>
2 
3 int main()
4 {
5     int *ptr=NULL;
6     ptr=(int *)0x67a9;
7     *ptr=0xaa66;
8     printf("%d
",*ptr);
9 }

以上代码在实际测试中,运行后立马出错,估计是测试的时候,"0x67a9"这个地址里面的内容被修改后电脑哪里出了问题,但是其实上面这种写法,已经实现了。

9、位操作,给定一个整型变量"a",写两段代码,第一个设置"a"的 bit 3,第二个清除"a"的 bit 3。在以上两个操作中,要保持其它位不变。

  1、a |= 0x1<<3;

  2、a &= ~(0x1<<3);

10、关于中断服务子程序的注意事项。评价下面的代码:

1 __interrupt double compute_area (double radius)
2 {
3     double area = PI * radius * radius;
4     printf("
Area = %f", area);
5     return area;
6 }

  上面这个中断服务程序有很多错误,下面,就以这个例子来对中断服务子函数做下总结:

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

2、ISR 不能传递参数,中段函数往往是由硬件产生的,不是由其它函数调用的,这样以来要返回值干嘛??返回给谁???所以上面的形参和返回值都是错的。

3、在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。

4、与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

11、代码例子 1,下面的代码输出是什么,为什么?

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

  这段代码的输出结果是:"> 6"。

  这段代码测试你是否懂得C语言中的整数自动转换原则,当表达式中存在有符号类型和无符号类型时,所有的操作数都会自动转换为无符号类型,因此"-20"转换成了一个正整数,它与 "a(=6)" 相加后,结果肯定大于 6。

12、评价下面的代码片段:

1 unsigned int zero = 0;
2 unsigned int compzero = 0xFFFF;
3 /*1's complement of zero */

  乍一看,没什么问题,其实也就是没什么问题。

  我刚看过网上那个人写到的这个问题后,搞明白了他想说什么。

  第一个变量赋值为"0",这段代码在哪写都对,但第二个变量,如果你试图给变量 "compzero"的每一位置 1,在16位系统中,这样写没错,“0xFFFF”刚好是16个 1,如果在32为系统中呢?那就不对了,这样写你只能给前16位或后16位置1,而且在8位系统中,还会被警告有溢出风险。如果你想让你得代码有好的可移植性,就是说,第二种变量赋值,在哪写都对,那应该这样写:

1 unsigned int compzero = ~0;

  这样写,就是把"0"取反,赋值给变量 "compzero"。

13、动态内存分配(Dynamic memory allocation)

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 
 4 int main()
 5 {
 6     char *ptr;
 7     if ((ptr = (char *)malloc(0)) == NULL)
 8     puts("Got a null pointer");
 9     else
10     puts("Got a valid pointer");
11 }

  上面代码会输出什么呢?

  看到"malloc(0)"参数填写的是0的时候,是不是以为malloc仍然后返回一个空指针?其实,就算申请的空间是0,malloc也仍然是起到作用了,仍然能返回一个有效的空间地址,所以这段代码返回的是:

14、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 两个指针。

15、晦涩的语法

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

原文地址:https://www.cnblogs.com/data-base-of-ssy/p/6897500.html