zoukankan      html  css  js  c++  java
  • [收藏] 宏工作原理以及典型面试10问

    关注、星标嵌入式客栈,精彩及时送达

    [导读] C语言中宏是非常有价值的语言特性之一,也是面试中必考察的要点之一,本文来分享总结一些关于宏的常见面试问题。希望能帮助到有需要的小伙伴们。

    宏工作原理

    以hello word程序为例来看看,将下述代码存成hello.c

    #include <stdio.h>
    #define STR "hello world"
    /*这是一个hello word程序*/
    int main(void)
    {
        printf("%s",STR);
        return 0;
    }
    

    为了说明问题,这里用下面的命令进行显式预处理,将得到hello.i文件,实际编译过程中,会自动完成。

    //gcc -E 生成预处理文件
    gcc -E hello.c -o hello.i
    

    来大致看看hello.i文件

    # 1 "hello.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    #删除很多行
    .......
    extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
    # 912 "/usr/include/stdio.h" 3 4
    extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
    
    extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
    
    extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
    # 942 "/usr/include/stdio.h" 3 4
    
    # 2 "hello.c" 2
    # 4 "hello.c"
    int main(void)
    {
        printf("%s","hello world");
        return 0;
    }
    

    上面这步操作做了三件事情:

    • 删除注释:删除所有注释。注释仅供程序员理解代码,注释对机器没有用。因此预处理器在预处理过程中会删除注释,因为注释在执行过程中是不需要的,也不会被执行。所以注释尽管写不影响程序的逻辑,当然写的过也未必是好事,过少也不是好事。个人理解一份好的代码应尽量少注释,应该通过合理的命名习惯,良好的编程风格来提高可读性,在一些关键复杂算法处则应清晰的加上注释。在hello.c中的注释  /*这是一个hello word程序*/  在预处理后被删除掉了。

    • 文件包含:包含程序需要的所有文件。C语言中使用#include,这是预处理器的指令,告诉预处理器包含指定文件的内容。例如#include将告诉预处理器将stdio.h中所有的内容包含进来。也可以使用双引号-#include “stdio.h” 注意:如果使用尖括号,则在编译器包含路径中搜索文件。如果文件名用双引号包起来,则搜索路径将扩展为除了编译器包含路径外的当前目录下。

    • 宏展开替换:比如上例中宏STR在预处理时就被展开替换了。宏有两种常见形式:

      大致说明了宏的工作原理,来看看一些常见的面试问题:

      • 不带参形式(有的地方也称对象形式object-like)。

        #define PI 3.1415926f
        
      • 带参形式(有的地方也称为函数形式function-like)。

        #define SQUARE(x) ((x)*(x))
        

    面试问题1

    如下代码,问有多少个"嵌入式客栈"会被打印?

    (A)  1 

    (B)  3 

    (C)  4 

    (D) 编译错误

    #include <stdio.h> 
    #define PRINT_HELLO(i, times) do  
           {  
             if (i++ < times)  
             {  
               printf("嵌入式客栈
    ");  
               continue;  
               }  
           }while(1) 
      
    int main() 
    { 
        PRINT_HELLO(0, 3); 
        return 0; 
    } 
    

    答案:D

    解析:PRINT_HELLO宏在预处理器时被扩展。宏展开后,if表达式变为:if(0 ++ <3)。0是一个常数,常数如何自增呢?,因此应用增量运算符会产生编译时错误。

    面试问题2

    下述代码的输出是什么?

    (A) 3

    (B) 5 

    (C) 3 或者 5 取决于X的值 

    (D) 编译错误

    #include <stdio.h> 
    #if A == 3 
        #define B 3 
    #else 
        #define B 5 
    #endif 
      
    int main() 
    { 
        printf("%d", B); 
        return 0; 
    } 
    

    答案:B

    解析:乍一看,输出似乎是编译时错误,因为尚未定义宏A,所以A是不等于3的,所以会将B定义为5。你如不信,也可以用上面的办法gcc -E hello.c -o hello.i来验证,或者编译运行一遍。

    面试问题3

    问:针对下述代码,哪个答案正确?

    (A) 嵌入式

    (B) 客栈 

    (C) 编译错误

    (D) 运行错误

    #include <stdio.h> 
    #define X 3 
    #if !X 
        printf("嵌入式"); 
    #else 
        printf("客栈");    
    #endif 
    int main() 
    { 
        return 0; 
    }
    

    答案:C 编译错误

    解析:程序编译三部曲:预处理、汇编、链接,那么在预处理时,上述代码就变成下面这样:

    #这里还有stdio.h的包含内容
    printf("客栈"); 
    int main()
    {
        return 0;
    }
    

    printf在main外面被调用了,所以编译会出错。

    面试问题4

    下述代码的输出应该是?

    (A) 嵌入式 

    (B) 客栈

    (C) 嵌入式 或 客栈

    (D) 编译错误

    #include <stdio.h> 
    #define IS_EQUAL(X, Y) X == Y 
    int main() 
    { 
        #if IS_EQUAL(X, 0) 
            printf("嵌入式"); 
        #else 
            printf("客栈"); 
        #endif 
        return 0; 
    } 
    

    答案:A

    解析:条件宏#if IS_EQUAL(X,0)扩展为#if X ==0。预处理结束后,所有未定义的宏均使用默认值0初始化。

    面试问题5

    下述代码的输出应该是?

    (A) 20 

    (B) 2000 

    (C) 0

    (D) 编译错误

    #include <stdio.h> 
    #define SQUARE(x) x*x 
    int main() 
    { 
      int x; 
      x = 2000/SQUARE(10); 
      printf("%d", x); 
      return 0; 
    } 
    

    答案:B

    解析:预处理器用10*10替换SQUARE(10),表达式变为 x = 2000/10 * 10,x的值计算为2000。如前所说,应定义如下:

    #define SQUARE(x) ((x)*(x))
    

    面试问题6

    下述代码的输出应该是?

    (A) 编译错误

    (B) %s Embedded Inn 

    (C) Embedded Inn 

    (D) %s Embedded Inn Embedded Inn

    # include <stdio.h> 
    # define scanf  "%s Embedded Inn " 
    int main() 
    { 
       printf(scanf, scanf); 
       return 0; 
    } 
    

    答案:D

    解析:在编译的预处理阶段之后,printf语句将变为。

    printf(“%s Embedded Inn”,“%s Embedded Inn”);
    

    面试问题7

    下述代码的输出应该是?

    (A) 编译错误

    (B) 嵌入式客栈 

    (C) 客栈客栈

    (D) 嵌入式嵌入式

    #include <stdio.h> 
    #define STR "嵌入式" 
    int main() 
    { 
      printf("%s",STR); 
      
      #define STR "客栈" 
      
      printf("%s",STR);
      return 0; 
    }
    

    答案:B

    解析:如果重新定义预处理程序指令,则预处理器不会给出任何错误,它可能会发出警告。预处理器在使用之前获取新值,并将其替换。

    面试问题8

    下述代码的输出应该是?

    (A) 100 

    (B) 编译错误 

    (C) 0 

    (D) 1

    #include<stdio.h>  
    #define ADHESION(x,y) x##y  
    int main()  
    {  
       int var1 = 100;  
       printf("%d", ADHESION(var,1));  
       return 0;  
    } 
    

    答案:A

    解析:运算符##称为“令牌粘贴(Token-Pasting)”或“合并(Merge)”运算符。它将两个符合合并为一个。因此在预处理之后,printf变为。

    printf("%d", var1); 
    

    面试问题9

    下述代码的输出应该是?

    (A) 6666.6

    (B) 666.6 

    (C) 编译错误

    (D) 无效值

    #include <stdio.h> 
    #define MAX 6666.6f
    int main() 
    { 
       float MAX = 666.6; 
       printf("%f ", MAX); 
       return 0; 
    } 
    

    答案:C

    解析:展开一看便知。

    int main()
    {
       float 6666.6 = 666.6;  //常数不可为左值
       printf("%d ", 6666.6);
       return 0;
    } 
    

    面试问题10

    下述代码的输出应该是?

    (A) 编译错误 

    (B) 嵌入式客栈

    (C) MAIN

    (D) main

    #include <stdio.h> 
    #define macro(n, a, i, m) m##a##i##n 
    #define MAIN macro(n, a, i, m) 
      
    int MAIN() 
    { 
        printf("嵌入式客栈"); 
        return 0; 
    }
    

    答案:B

    解析:不注意可能会选A,认为将MAIN敲成大写了,其实仔细一看,通过前面两个宏以及粘连操作符##将MAIN替换成main,所以没有问题,这个题目比较骚,主要考察细心以及粘连操作符。

    总结一下

    面试小提示:实际笔试中,只有掌握了宏的基本操作原理,以及宏预处理的本质,在解题时细心展开,一般而言不会有什么问题。

    本文总结了宏的基本工作原理,以及10个比较典型的面试问题,相信对于宏理解不深的盆友会有些许帮助。

    本文辛苦原创,如喜欢请点赞/在看/分享支持,不胜感激!

    END

    往期精彩推荐,点击即可阅读

    ▲Linux内核中I2C总线及设备长啥样? 

    ▲学Linux驱动:应先了解总线驱动模型

    ▲看思维导图:一文带你学Verilog HDL语言

  • 相关阅读:
    Linq 入门 顺带 Func与Action
    关于asp.net 的一些好资料地址 , 防止丢失!
    Sql日期时间格式转换 备用
    自己动手写 ASP.NET MVC 分页 part1
    怎么做好一个美食排行榜的用户投票功能?
    「要买车网」免费获取汽车电商要买车网购车优惠券
    MVC Ajax Form & Ajax Valida(笔记)
    C# 序列化高级用法
    我与葡萄城的故事
    生成分布式随机ID
  • 原文地址:https://www.cnblogs.com/embInn/p/14038108.html
Copyright © 2011-2022 走看看