程序环境和预处理


目录

前言

1. 程序的翻译环境和执行环境

2. 详解编译+链接

2.1 翻译环境

2.2 运行环境

3. 预处理详解(重点)

3.1 预定义符号

3.2 #define

3.2.1 #define 定义标识符

3.2.2 #define 定义宏

3.2.3 #define 替换规则

3.2.4 #和##

3.2.5 带副作用的宏参数

3.2.6 宏和函数对比

3.2.7 命名约定

3.3 #undef

3.4 命令行定义

3.5 条件编译

3.6 文件包含

3.6.1 嵌套文件包含

3.6.2 头文件被包含的方式

4. 其他预处理指令


前言

写出test.c这样的文件,为什么就可以运行产生想要的结果如加,减、乘、除等?
其实一个C语言程序test.c最终会编译(编译器经过复杂的处理)成test.exe这样的文件,该文件是可执行文件,这个文件才会运行产生我们想要的效果。
那么一个.c代码的文件到底是怎么产生.exe这样的可执行程序呢?这些动作又有什么意义呢?
此博文探索的就是程序的整个编译过程——程序的环境和预处理。

1. 程序的翻译环境和执行环境

在ANSI C的任何一种实现中,存在两个不同的环境。

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码。

理解:

ANSI C是遵循美国国家标准总局定义的C语言标准的C语言,它的任何一种编译器的实现中,都存在两个不同的环境。即任何一个C语言程序它都有两个环境,分别是翻译环境和执行环境。

支持C语言就需要有编译器,不同的厂商会造成不同的编译器,如VS,gcc,clong这些编译器。
如果编译器支持C而且又遵循ANSI C的标准,则这个编译器就必须存在两个不同的环境——翻译环境和运行环境。

当写出一个.c代码文件时,.c要变成.exe可执行程序,需要经过一个翻译环境,当产生可执行程序时则需要运行才产生我们想要的效果,这里依赖运行环境

test.exe文件是二进制文件,放的都是二进制的指令,也称机器指令,机器能够识别和读懂的是二进制;而test.c中放的是C语言的源代码,是肉眼可以读懂的,翻译环境就是把C语言的这样的源代码翻译成机器能够读懂的二进制指令当翻译成二进制指令或机器指令的时候再由运行环境去处理二进制指令产生我们想要的结果

即C语言代码能被运行产生结果的大概逻辑——一个C语言的源代码经过翻译环境翻译成二进制指令、然后二进制指令经过运行环境,即计算机的运行,计算机能够识别二进制,对二进制指令进行解读和理解、运行就会产生想要我们想要的结果。

翻译环境又被拆分为两个大的环境,(一个翻译环境可以解读为两个过程)分别是编译和链接。编译过程中依赖的工具是编译器;链接过程中依赖的工具是链接器
C语言代码经过编译处理会生成一个目标文件,而目标文件又经过链接才生成可执行程序。
目标文件是什么?
代码在编译之后按Ctrl+F7就会生成目标文件(.obj文件,在Windows环境中以.obj为后缀的就是目标文件),在本程序路径下的Debug文件夹下。
若工程中有test.c,add.c、sub.c、mul.c、div.c……等很多.c文件,这么多的.c文件都会单独经过编译器处理,test.c经过编译器处理会生成一个test.obj文件;add.c经过编译器处理会生成一个add.obj文件;sub.c经过编译器处理会生成一个sub.obj文件;mul.c经过编译器处理会生成一个mul.obj文件;div.c经过编译器处理会生成一个div.obj文件。

组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人
的程序库,将其需要的函数也链接到程序中。

即链接过程中(目标文件加上链接库,链接库也要被链接进去),每一个源文件都会经过编译器单独处理最后生成各自的独立的目标文件,链接器就会把生成的这些目标文件和链接库链接在一起生成.exe可执行程序。
其实只有一个编译器,只不过所有的源文件都会经过编译器处理。

链接库是什么?
写代码时如写的printf()函数是C语言提供给我们使用的库函数,库函数在哪存放呢?库函数在如MSDN工具的文档中看:搜索printf()函数,在下面出现的LIBRIES就是库,各种.LIB文件就是库,在代码中使用了printf()函数,此时若要正常使用printf()函数,函数又在库中存放,编译器就像链接目标文件一样把链接库链接进去,最终在可执行程序中想要使用库函数时才能找到这个库函数,即链接的时候会把库函数中的库也链接进去,链接库中就提供了相关的函数,此时在可执行程序中就可以正常使用。
链接库不是用来识别头文件的,链接库是为了让我们使用的库函数有源头,知道库函数在哪存放。
相关知识补充:
C语言的库函数放在LIB目录里面,是存放在函数库中的函数,库函数是将函数封装入库,供用户使用的一种方式。方法是把一些常用到的函数编完放到一个文件里,供不同的人进行调用。调用的时候把它所在的文件名#include<>加到里面就可以了。一般是放到lib文件里的。
链接的时候链接的是.LIB文件(静态库),就可以使用函数。源代码都被变成静态库供人使用,静态库就是一种封装。
(静态库的后缀是.LIB,没有静态库时静态库中提供的函数是不可以使用的)

总结:

由源代码到产生结果的过程:
在工程中写出的各种.c文件经过编译器进行编译处理会生成各自对应的目标文件,多个目标文件再加上链接库一起经过链接器进行链接处理最终生成可执行程序,而可执行程序是二进制文件,里面放的是二进制指令即机器指令,这些机器指令是可以被硬件直接解读的,硬件环境加上操作系统的解读依赖运行环境就可以生成我们想要的结果。

编译整个过程展开又可以分成3个过程:预编译编译汇编汇编之后产生的是目标文件

源文件是怎么进行预编译的?怎进行编译的?怎么进行汇编的?最后是怎么进行链接的?

2. 详解编译+链接

2.1 翻译环境


在Linux环境中演示,因为gcc编译器可以看到每一个步骤的效果,所以用gcc C语言编译器演示程序的编译和链接过程——每完成一次动作看结果。

在Linux环境中:
LS是列出当前目录底下的东西。
vim是编辑器,就像Windows中的记事本。如vim test.c是打开test.c文件。

1、怎么进行预编译呢?

-E选项,编译test.c,在Linux中用gcc编译器就是:gcc -E test,或gcc test -E

理解:-E这个选项去执行命令时,预处理就开始了,预处理产生的结果打印在屏幕上。若把预处理的结果放到文件中,则:gcc -E test.c -o test.i-o的意思是输出一个文件,输出的文件夹是test.i文件,生成的test.i文件放的就是预处理之后的结果。

预处理的命令:gcc -E test.c -o test.i,只要执行这个命令,就会进行预编译

预编译做了什么呢?

vim.test.i,打开test.i文件会发现相对test.c有很多代码,但是test.i文件中没有头文件的包含(没有#include <stdio.h>),是因为test.i文件中把头文件<stdio.h>里的内容都包含起来了,把stdio.h头文件中的内容都进行了展开。

在Linux中找头文件:在usr路径下有include,在这里面有stdio.h的头文件。用vim工具打开头文件

stdio.h头文件,这样可以看stdio.h头文件的内容。

发现这两个文件的内容对比一样。

所以,预编译期间进行了头文件的展开。

预编译(预处理)做的事:

  1. 头文件的包含,处理的是#include这个指令。#include本身就是一个预处理指令或者预编译指令,包含头文件的全部内容。
  2. 删除注释(注释是被空格替换的,注释不见了)。注释是给读代码的人看的,是写给程序员的,编译器不需要看注释。
  3. #define定义符号(宏)的替换。替换之后也会把#define定义的符号删掉。

即预处理做的事是文本操作——拷贝,删除,替换。

预编译也称预处理

以上生成了test.c文件。

2、怎么进行编译呢?

test.i进行处理:

-S选项,gcc test.i -S,或gcc -S test.i,此时在当前目录下多了test.s文件。

test.s文件中放的是汇编代码,是汇编指令。

编译这个过程做了什么呢?

因为在test.i文件中放的还是C的代码,而对test.i文件进行-S处理之后生成test.s文件,test.s中已经是汇编代码了,说明编译这个过程是把C语言代码转换成汇编代码。

是怎么做到的呢?

——是进行相关的动作:

  1. 语法分析;
  2. 词法分析;
  3. 语义分析;
  4. 符号汇总。

即编译过程中做的事:分析、理解C语言代码写出对应的汇编语言。(提取C语言中每一个独立语义的符号,根据语法和对代码的解析生成语法树……过程复杂)

扩展阅读书籍:

《编译原理》——(晦涩难懂)讲编译器是怎么造出来的,编译器的工作原理;

《程序员的自我修养》——(通俗易懂)讲解代码编译、链接过程的细节,包括如何建立一个语法树,如何进行解析等等。

3、怎么进行汇编呢?

-c(小写)选项,指令是:gcc -c test.s对test.c文件进行相关的处理,此时在当前目录下多了test.o文件。

在Linux环境中,以.o为后缀的是目标文件。

在Windoxs环境中,以.obj为后缀的是目标文件。

对以上所有操作的代码展示

打开test.o文件后,发现内容是不认识的,是二进制的信息。

所以汇编这个过程是把汇编代码转换成二进制指令

是怎么做到的呢?

——讲一个动作,形成符号表。符号表有什么意义,作用是什么?

与上述同理,执行指令:

gcc add.c -E -o add.i,对add.c文件进行-E处理,让它生成add.i文件。

gcc -S add.i,对add.i文件进行-S处理,结果是生成了add.s文件。

gcc -c add.s,对add.s文件进行-c处理,结果是生成了

  • 32
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 28
    评论
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值