c c++面试c工程开发之宏定义和条件编译
多数c语言的初学者对c工程开发过程各个阶段的作用理解不到位,而这方面的的知识又是实际开发过程中经常用到的技能点,所以就成为面试考察中一个重要的考察方面。例如:头文件的作用、头文件的内容;链接的作用和意义;条件编译的作用等等此类问题,接下来将c工程开发过程中的各种问题进行总结使大家能够自如应对这方面的面试题目。
c工程开发经过以下几个阶段:
编辑--》预处理--》编译--》汇编--》链接--》运行--》调试
预处理阶段通常要完成三件事情,用头文件内容替换#include语句;宏体替换宏名;根据条件编译选择合适的代码段,在上一篇中已经讲解了头文件的作用和意义,本篇主要讲解宏定义和条件编译的概念和作用,这在开发过程是比较常用的技术,但是多数面试者由于项目经验有限对此部分内容理解上比较模糊,本次主要从应用角度阐述这两个知识点引入的原因和作用。
一、 宏定义:分为不带参数的宏和带参数的宏
1、不带参数的宏:
格式: #define 宏名 宏体
作用: 给常量起名字或者代替固定代码段,增强程序可读性并方便编程
2、带参数的宏:
格式: #define 宏名(参数) 宏体
作用: 代替比较短的函数,函数代码3句以内,这样做的优点是能够避免频繁调用函数带来系统的开销(保存断点状态和回复断点状态)
面试题目1:设计一个宏,求两个数的最大值
#define MAX(x,y) x > y ? x:y
以上答案被认为是错误的,正确的答案:
#define MAX(x,y) (x) > (y) ? (x):(y)
所有参数两边都要加括号,这样做的目的是为了保证安全,因为宏替换发生
在预处理阶段,对数据类型不做任何检查,只是机械进行相关的替换,不加括号很容易造成错误。
面试题目2:设计两个宏:将一个32位寄存器某一个为置1,或清零
将data的第12位设置为1:SETBIT(data,12)
将data的第28位清0: CLEARBIT(data,28)
两个宏设计如下,用位运算解决
#define SETBIT(data,n) (data) = (data) | 0x01<<(n)
#define CLEARBIT(data,n) (data) = (data) & ~(0x01<<(n))
注意:参数两边都需要加括号
二、条件编译
1、根据一个宏是不是被定义过作为条件,选择保留部分代码,抹除部分代码
2、条件编译的基本形式:
形式1:
#define 宏名
#ifdef 宏名
代码
#endif
功能:宏名定义过了,则保留代码段,否则舍弃
形式2:
#ifndef 宏名
代码
#endif
功能:宏名未定义过了,则保留代码段,否则舍弃
形式3:
#ifdef 宏名
代码1
#else
代码2
#endif
功能:根据宏名是否被定义过作为条件,在代码1和代码2中选择保留其中之一,另外一个舍弃
3、宏定义的作用
作用1:用在头文件中,防止重复包含
例如:
头文件func.h内容:
#ifndef _FUNC_H_
#define _FUNC_H_
头文件内容
#endif
这样当头文件被一个文件包含多次(开发中由于头文件中可以再包含头文件,此种情况比较常见)
按照以上格式编写的头文件,不管包含语句包含的多少次该头文件,由于条件编译的作用使的头文件
内容仅包含依次
例如:#include "func.h"
#include "func.h"
预处理以上两句被替换为:
#ifndef _FUNC_H_
#define _FUNC_H_
头文件内容 //该部分内容保留
#endif
#ifndef _FUNC_H_
#define _FUNC_H_
头文件内容 //该部分内容舍弃
#endif
作用2:用于注释程序
c程序开发过程中不能嵌套注释,这样在编写或者修改较大的工程中时就显得极为不方便,可以用条件编译实现嵌套注释。
例如如下的场景,如果需要注释如下的代码,则必须用条件编译来实现
形式如下:
#define DEBUG
#ifdef DEBUG
//
/*
----
----
*/
----
-----
//
/*----
-----
-----*/
-----
----
#endif
DEBUG作为注释的控制标志,
#define DEBUG 则注释所有代码
//#define DEBUG 则取消注释
作用3、用于调试程序
程序开发过程中为了调试程序,寻找其中的逻辑错误,经常需要打印内存的值和位置,因此系统给开发者提供了三个宏,可以在开发过程中直接使用。
__FILE__:代表文件名
__FUNCTION__:代表函数名
__LINE__:代表行号
printf("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__)
作用:打印本句所在的文件名、函数名和行号,利用该语句寻找程序中逻辑错误
如果由于开发环境的限制,不能使用调试器,则灵活的使用以上三个宏,则能起到事半功倍的效果。
示例代码如下:
#define DEBUG //加入所有调试信息
//#define DEBUG //取消所有的调试信息
#ifdef DEBUG
#define PRINT printf
#else
#define PRINT(...)
#endif
int main()
{
int data1 = 3,data2 = 4;
data1 = data1 + 1;
PRINT("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
data1++;
PRINT("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
data2 = data2 + 3;
data2++;
PRINT("%s %d\n",__FUNCTION__,__LINE__);
data2 = data1 + data2;
PRINT("data2 = %d\n",data2);
data1 = data2 + 4;
PRINT("data1 = %d,data2 = %d\n",data1,data2);
return 0;
}
以上的代码,DEBUG宏是否被定义过作为是否加调试信息依据,这是某些工程中典型的一种
用法。
如果gcc编译该程序,程序中不需要对DEBUG进行定义,用参数-D控制
gcc ex1.c -o res -D DEBUG DEBUG被宏定义过编译该程序
gcc ex1.c -o res DEBUG宏未定义过编译该程序