一篇文章学会c语言最后一章——预处理及程序运行过程

本文详细介绍了C语言的预处理及程序运行过程,包括翻译环境中的预处理、编译、汇编和链接阶段。预处理部分涵盖宏定义、条件编译和头文件包含等,讲解了宏的使用技巧和注意事项,以及在Linux环境下gcc的特殊操作。同时,文章讨论了宏与函数的优劣对比,以及内联函数inline的概念。
摘要由CSDN通过智能技术生成

预处理及程序运行过程



一个程序从写的代码到最后运行结果的显示,大致会经历两个过程: 翻译环境执行环境

程序运行过程

翻译环境

请添加图片描述

翻译过程

所谓的翻译环境是将一个.c文件编译成.exe可执行程序的过程,vs的编译器是一个集成开发环境,意思就是其中比较复杂的过程编译器自动完成了,直接把c文件编译后的最终结果exe推出来,所以正常情况下看不到中间过程,在linux环境下的gcc模式中可以用命令行的方式看到翻译过程中间的文件

ANSI C是c语言标准的运行环境

预处理

这里的#define就已经把文本替换了,和typedef(在编译的语法分析的时候替换的)不一样

注释编译器不会看的,会直接删除的,因此随便写注释,注释不会加重程序的运行时间

编译 ----《编译原理》
  • 语法分析:for,while,if啥的化简成基本的语句,就像加减乘除都可以用加法实现一样,这里是都变成基本语句

  • 词法分析:判断关键词都有什么作用

  • 语义分析:让程序知道你要干什么,为什么这么写

汇编

这个阶段,所有的变量,函数名,各种参数都变成了二进制的符号

这个阶段每个文件里面都是各种变量的符号,比如Add函数有ab俩变量,那么这个函数里面就有ab的符号,也就是说把每个函数的参数什么的都集合了,是一段一段的

链接器

源文件(.c)经过编译器的编译会生成对应的目标文件(.obj),有了目标文件之后还要去寻找在目标文件中涉及的标准库和程序员自定义的函数,将目标文件和链接库融合的步骤就是链接器的工作。

请添加图片描述

这里是上课讲的通讯录,我们创建了三个文件,对应不同的过程,这三个文件最终实现一个工程:通讯录。

这三个文件即使有头文件链接,在编译器编译的过程也是分开独立的,只有他们在链接器组合拼接的时候才被合并,这里可以通过命令行的方式,使编译过程停止到链接的前一步。

这些目标文件(.obj)和标准库组合起来后就是可执行程序了

还要删除汇编阶段的符号汇总,因为此时会有很多重复的内容,要去重

运行环境

就是程序运行时会占用很多内存,此时就需要一个运行环境去让程序跑起来

程序要创建临时变量,就去栈区申请空间,去堆区,静态区啥的

到此阶段及之前涉及的堆栈就是栈堆就是堆

预处理

预定义

有些东西是编译器或者库函数自带的符号,就是说之前重命名的时候,程序突然挂掉或者语法错误的时候,说符号重定义,意思就是这个符号库函数已经用过了,你不能再定义了,比如EOF已经被定义成-1了,就不能再定义了,不然程序就会报错

还有其他比较有价值的符号,这些符号比较适合写日志,因为写日志的时候有一些比较繁琐的信息,像是时间、源文件名称啥的

int main()
{
   
    printf("%s", __FILE__); //进行编译的源文件
    printf("%d", __LINE__); //文件当前的行号
    printf("%s", __DATE__); // 文件被编译的日期
    printf("%s", __TIME__); // 文件被编译的时间
    printf("%s", __STDC__); // 如果编译器遵循ANSI C,其值为1,否则未定义//vs编译器不支持,也不识别
    return 0;
}

有了这些信息可以很方便的记录东西

#define

在定义标识符时一般采用全大写格式,是为了与函数区分,函数标识符(函数名)默认是全小写格式

  • 可以进行简单的文本替换

#define MAX 100 这里的MAX就是标识符

#define reg register 也可以给关键字重命名,嫌unsigned int太长,可以替换成uint

#define do_forever for(;;) 也可以写一些简短的格式,这里是替换了个for的死循环

#define DEBUG_PRINT printf("file:%s\tline:%d\t \ date:%s\ttime:%s\n" ,\ __FILE__,__LINE__ , \ __DATE__,__TIME__ )

如果定义的内容过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠“\”(续行符)。

  • 一般不在重定义的时候写分号,容易出现语法错误

在define重定义的时候可以替换参数

类似#define SQUARE( x ) x * x,这里是说,如果在语句中出现了SQUARE( 3 )吧,就会被替换成3 * 3

  • 使用宏极其容易出现语法错误
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

这里的内容将被替换为printf("%d\n",a + 1*a + 1);,也就是5+1*5+1=11

除非这样定义#define SQUARE(x) (x) * (x),加上括号后,就是printf("%d\n",(a + 1)*(a + 1));也就是(6)*(6)=36;

字符串中的宏不会被替换printf("MMM = %d",MMM);字符串中的MMM不会被替换,但是“,”后面的MMM会被替换

#和##

先了解一个概念

int main()
{
   
    printf("hello zhao\n");
    printf("hello "                 "zhao\n");//字符串可以用""分割
    printf("hello ""zhao\n");
    return 0;
}

这里的三个输出结果相同,在字符串中插入""可以拼接两个字符串

知道了这个概念之后,再介绍“#”

#X可以替换X这个内容对应的字符串

在define定义的宏中,有如下方式

#define PPP(X) printf("the "#X" is %d\n",X)

int main()
{
   
    int aaa = 10;
    PPP(aaa); //就是"the ""aaa"" is 10",这里的#X被替换成了"aaa",X还是10

    int bbb = 20;
    PPP(bbb); //就是"the ""bbb"" is 20",这里的#X被替换成了"bbb",X还是20

    int ccc = 30;
    PPP(ccc); //就是"the ""ccc"" is 30",这里的#X被替换成了"ccc",X还是30
    return 0;
}

这段代码是为了演示#在宏中的作用,这个**#X可以替换X这个内容对应的字符串**,第一个传的是10,但是对应的字符串是“aaa”

因此#X就被替换成“aaa”,而X正常替换成10,这就是宏的优点

宏没有格式限制,使用灵活

上面的代码中只能替换整型还是有点限制,可以再加一个参数,使它变的更万能

#define PPP(X
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值