33.C语言宏定义和预处理


33.1.源码到可执行程序过程
(1)源码.c文件->(编译)->elf可执行程序。
(2)源码.c->(编译)->目标文件.o->(链接)->elf可执行程序。
(3)源码.c->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序。
(4)源码.c->(预处理)->预处理过的.i源文件->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序。
(5)预处理用预处理器,编译用编译器,汇编用汇编器,链接用链接器,这些工具再加上额外的会用到的工具组成编译工具链,gcc就是某个编译工具链。


33.2.预处理的意义及编程应用
(1)编译器本身的主要目的是编译源代码,将C的源代码转化成.S的汇编代码;编译器聚焦核心功能后,就剥离出某些非核心的功能到预处理器;预处理器帮编译器做一些编译前的杂事。
(2)预处理阶段的主要工作:头文件包含(#include);注释;条件编译(#if #elif #endif/#ifdef);宏定义。
(3)配置gcc实现只预处理不编译:gcc编译时可以给某些参数来设置,譬如gcc xx.c -o xx即指定可执行程序的名称,譬如gcc xx.c -c -o xx.o即指定只编译不链接,譬如gcc -E xx.c -o xx.i即实现只预处理不编译;通常情况下我们没必要只预处理不编译,但有时该技巧可帮助我们研究预处理过程,帮助debug程序。
(4)宏定义被预处理后的现象:第1宏定义语句本身不见了,则编译器根本就不知道#define宏;第2typedef重命名语言没变,则typedef是由编译器来处理的。


33.3.头文件包含
(1)#include<>和#include”“的区别:<>专门用来包含系统提供的头文件;”“用来包含程序员自己写的头文件。
(2)<>包含头文件:C语言编译器只会到系统指定目录(编译器/操作系统配置的目录,譬如在ubuntu中是/usr/include目录,编译器允许用-I来附加指定其它的包含路径)去寻找该头文件,若没找到则提示该头文件不存在,注意编译器不会在当前目录下寻找该头文件。
(3)”“包含的头文件,编译器默认会先在当前目录下寻找该头文件,若没找到则再到系统指定目录去寻找,若还没找到则提示该头文件不存在。
(4)总结:若包含系统指定的自带的头文件用<>;若头文件是自己写的存放在当前目录下用”“;若头文件是自己写的但集中存放在某个目录下将来在编译器中用-I参数来寻找,此时头文件包含用<>。
(5)头文件包含的真实含义:在包含头文件的那一行,将头文件的内容原地展开替换包含头文件的语句,替换过程在预处理中进行。


33.4.注释和条件编译
(1)注释是给人看的,不是给编译器看的;在预处理阶段时预处理器会拿掉程序中所有的注释语句,到了编译器编译阶段程序中已经没有注释了。
(2)条件编译即有时候我们希望程序有多种配置(源代码编辑阶段给每个配置开关编写了相应的源码),在源代码级别去修改配置开关来让程序编译出不同的效果。
(3)条件编译中条件判断标准:#ifdef和#if;#ifdef XXX(其判断标准是XXX该符号在本语句之前是否被定义,只要定义了则表达式成立(#define XXX或#define XXX 12或#define XXX YYY));#if(条件表达式)(判定标准是()中的表达式是否为true还是flase,类似C中的if语句)。


33.5.宏定义的规则和使用解析
(1)宏定义的解析规则:在预处理阶段由预处理器进行原封不动的替换;宏定义替换会递归进行,直到替换出来的值本身不再是某个宏为止。
(2)宏定义格式:第1部分是#dedine,第2部分是宏名,第3部分为剩下的全部东西。
(3)宏可以带参数(带参宏),带参宏的原理类似带参函数,在定义带参宏时,每个参数在宏体中引用时都必须加括号,最后整体再加括号,括号缺一不可。
(4)宏定义示例1:MAX宏,求2个数中较大的一个:#define MAX(a, b) (((a)>(b)) ? (a) : (b))(使用三目运算符;括号的使用)。
(5)宏定义示例2:SEC_PER_YEAR宏,用宏定义表示一年中有多少秒:#define SEC_PER_YEAR (365*24*60*60UL)(当某个数字直接出现在程序中时,其类型默认是int;一年有多少秒该数字超过了int类型存储的范围)。


33.6.带参宏和带参函数的区别
(1)宏定义是在预处理期间处理的,而函数是在编译期间处理的;宏定义最终是在调用宏的地方把宏体原地展开,而函数是在调用函数处跳转到函数中去执行,执行完后再跳转回来;宏定义是原地(没有调用开销),而函数是跳转执行再返回(有较大的调用开销);则宏的优势是没有调用和传参开销,当函数体很短可以用宏定义来替代,以提高效率。
(2)宏定义不会检查参数的类型,返回值也不会附带类型;而函数有明确的参数类型和返回值类型,当我们调用函数时编译器会帮我们做参数的静态类型检查。
(3)宏和函数各优劣:若代码比较多用函数适合而且不影响效率(编译器会进行静态类型检查);但若代码较短就适合用带参宏(宏不会检查参数类型)。


33.7.内联函数和inline关键字
(1)内联函数通过在函数定义前加inline关键字实现。
(2)内联函数本质上是函数,所以有函数的优点(编译器做参数静态类型检查);其也有带参宏的优点(原地展开没有调用开销);则几乎可认为内联函数就是带了参数静态类型检查的宏。
(3)当函数内函数体很短的时候,我们既希望利用编译器的参数类型检查来排错,而且还希望没有调用开销时,最适合使用内联函数。


33.8.宏定义来实现条件编译
(1)程序有DEBUG版本和RELEASE版本,区别就是编译时有无定义DEBUG宏。
(2)#define、#undef、#ifdef举例:
这里写图片描述


33.preprocess
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:C语言预处理详解
 * 功能:通过预处理.i文件解释#define和typedef的本质区别。
 */

#if 0
32.preprocess.i文件内容:
# 1 "33.preprocess.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "33.preprocess.c"
# 13 "33.preprocess.c"

typedef char * PCHAR

int main(int argc, char **argv)
{
 char * p1, p2;
 PCHAR p3, p4;

 return 0;
}
#endif 

#define pchar char * 
typedef char * PCHAR;

int main(int argc, char **argv)
{
    pchar p1, p2;
    PCHAR p3, p4;

    return 0;
}

33.preprocess_if_ifdef
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:C语言预处理详解
 * 功能:条件编译#ifdef和#if是使用方法。
 */
#include <stdio.h>

#define NUM
#define PRE 1

int main(int argc, char **argv)
{
    // 演示条件编译#ifdef的用法
    int a = 0;
    #ifdef NUM                  // 若前面语句中有定义NUM该符号,则执行下面的语句
    a = 11;
    printf("#ifdef NUM.\n");
    #else                       // 若前面语句中没有定义NUM该符号,则执行下面的语句
    a = 22;
    printf("#else\n");
    #endif
    printf("a = %d.\n", a);

    // 演示条件编译#if的用法
    int b = 0;
    #if (1 == PRE)              // 表达式成立,则执行下面的语句
    b = 55;
    printf("#if (1 == PRE).\n");
    #else                       // 若表达式不成立,则执行下面的语句
    b = 66;
    printf("#else\n");
    #endif
    printf("b = %d.\n", b); 

    return 0;
}

33.macro
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:C语言预处理详解
 * 功能:演示宏定义的各种用法。
 */
// 演示宏的递归替换
#define M       10  
#define NUM     M

// 宏定义示例1:MAX宏,求2个数中较大的一个
#define MAX(a, b) ((a)>(b) ? (a) : (b))

// 宏定义示例2:SEC_PER_YEAR宏,用宏定义表示一年中有多少秒
#define SEC_PER_YEAR (365*24*60*60UL)    

// 宏定义来实现条件编译
#define DEBUG
//#undef DEBUG      // 注销DEBUG宏,若前面有定义DEBUG宏则取消该宏

#ifdef DEBUG
#define debug(x) printf(x) 
#else
#define debug(x)    
#endif

#include <stdio.h> 

int main(int argc, char **argv)
{
    // 演示宏的递归替换
    int a = NUM;
    printf("NUM = %d.\n", a);               // NUM = 10.

    // 宏定义示例1:MAX宏,求2个数中较大的一个
    int b = MAX(5+9, 10-4);
    printf("MAX(5+9, 10-4) = %d.\n", b);    // MAX(5+9, 10-4) = 14.

    // 宏定义示例2:SEC_PER_YEAR宏,用宏定义表示一年中有多少秒
    unsigned long int c = SEC_PER_YEAR;
    printf("SEC_PER_YEAR = %ld\n", c);      // SEC_PER_YEAR = 31536000

    // 宏定义来实现条件编译 
    debug("This is a debug test.\n");

    return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值