zoukankan      html  css  js  c++  java
  • 总结下网上的嵌入式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。
    如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。

  • 相关阅读:
    [转] Akka实战:构建REST风格的微服务
    [转] Node.js的线程和进程
    [转] Spring Integration 系统集成
    NodeJS使用SSL证书
    Tomcat SSL证书安装配置
    [转]【NODE】用WS模块创建加密的WS服务(WSS)
    [转] Spring Boot实战之Filter实现使用JWT进行接口认证
    [转] 前后端分离之JWT用户认证
    [转] 使用 Java8 Optional 的正确姿势
    [转] SpringBoot RESTful 应用中的异常处理小结
  • 原文地址:https://www.cnblogs.com/data-base-of-ssy/p/6897500.html
Copyright © 2011-2022 走看看