[收藏] 宏工作原理以及典型面试10问
宏工作原理
以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('嵌入式客栈\n'); \ 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.6fint 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个比较典型的面试问题,相信对于宏理解不深的盆友会有些许帮助。
—— The End ——