Skip to content

Latest commit

 

History

History
210 lines (139 loc) · 5.5 KB

C语言由源代码生成可执行文件的过程.md

File metadata and controls

210 lines (139 loc) · 5.5 KB

1.运行 C 程序的步骤

  • 编写 C源程序
  • 编译预处理:包含头文件、宏定义及宏展开、条件编译、特殊符号处理
  • 编译
  • 优化程序
  • 汇编程序
  • 链接程序:
  • 生成可执行文件

2.编写源程序

按照业务逻辑和 C语言的语法规则编写源代码。

3.编译预处理

2.1 文件包含

文件包含:一个源文件可以使用#include指令,将另一个源文件的全部内容包含到本文件中,插入到当前的位置。

#include <abc.h>	//标准方式(到存放 C 库函数头文件的目录中寻找要包含的文件)
#include "abc.h"	//先在用户当前目录寻找要包含的文件,找不到再按标准方式查找

常用的库函数头文件:

函数 头文件
数学函数 math.h
字符串函数 string.h
字符函数 ctype.h
输入输出函数 stdio.h
动态分配内存函数 stdlib.h 或 malloc.h(和C编译系统有关)

2.2 宏定义及宏展开

宏定义

用一个指定的标识符(宏名)代表一个字符串,宏名习惯大写,可读性要好(见名知义),尽量集中放到源程序的最前面。使用宏是为了提高程序的通用性(一改全改)、避免重复书写某些字符串。一般用宏来代替简短的表达式。有的问题,用宏和函数都可以。善用宏定义可以简化程序。

宏展开

将宏名替换成字符串的过程。宏展开仅做简单的置换,而不做正确性检查。

  • 不带参数的宏定义
#define 标识符 字符串						//不带参数的宏定义

#define PI 3.1415926					//定义一个宏 PI 代替字符串 3.1415926

#define VERSION "Version 5.3 Copyright(c) 2020"		//使用宏代替一个字符串

#define COUNT 25
#define PRICE 9.9
#define TOTAL (COUNT * PRICE)			//宏定义可以嵌套(可以层层置换)
  • 带参数的宏定义
#define 标识符(参数表) 字符串			  //带参数的宏定义(除了进行字符串替换,还有进行参数替换。)

#define PRINT(x) printf(""%d\n",x);		//带参数的宏

#define SQUARE(x) ((x)*(x))				//带参数的宏:通过括号保证宏和参数的完整性
  • 带参数的宏可能产生副作用
int a = 11;
SQUARE(a++)	//本意是想计算 11*11 后再自增 a,但实际进行的计算是 11*12
    
SQUARE(a)
a++;		//解决:把可能产生副作用的代码移动到宏调用之外即可

注:

  1. #为字符串化运算符。
  2. ##运算符用于把两个参数连接到一起。
  3. #undef用于终止宏定义的作用域,即取消前面定义的宏定义。

2.3 条件编译

条件编译的作用是提高 C 源程序的通用性,以及方便调试,可根据需要选用不同的形式。

形式 1

若所指定的标识符已经被#define 指令定义过,则在程序编译阶段对程序段 1 进行编译;否则编译程序段 2。

#ifdef 标识符		//ifdef 表示 if defined
	程序段 1		//语句组或指令行
#else
    程序段 2  		//可以没有 #else 的部分
#endif

形式 2

若所指定的标识符未被#define 指令定义过,则在程序编译阶段对程序段 1 进行编译;否则编译程序段 2。

#ifndef 标识符		//ifndef 表示 if !defined
	程序段 1
#else
    程序段 2  
#endif

形式 3

当指定的表达式为真(非零)时就编译程序段 1,否则编译程序段 2。

#if 表达式			//可以事先给定条件,使程序在不同的条件下执行不同的功能。
	程序段 1
#else
    程序段 2  
#endif

【例1】使源程序适用于不同类型的计算机系统

#ifdef COMPUTER_A				//如果已定义过COMPUTER_A
	#define INTEGER 16			//编译此指令行
#else							//否则
    #define INTEGER_SIZE 32		//编译此指令行
#endif

【例2】方便调试(开关作用,调试完之后在源代码中删去或注释掉#define DEBUG即可)

#ifdef DEBUG					//若前面有#define DEBUG 指令则输出,以便调试分析
	printf("Print something useful to the console.\n");
#endif

【例 3】防止重复包含头文件:如果已经包含了头文件 abc.h 的内容,则不再包含该头文件,否则包含该头文件的内容。

//stdio.h
#ifndef _STDIO_H_
#define _STDIO_H_
...//头文件内容
#endif

4. 模块化编译链接

【例】计算并输出前 10 个Fibonacci数。涉及的2个源文件内容如下:

//源代码文件 1 main.c
#include <stdio.h>

int Fib(int n);

int main() {
    
    int n;
    printf("Fibonacci Series:");
    for(n = 0;n < 10;n++)
        printf("%d ", Fib(n));
    
    printf("\n");
    return 0;
}

//源代码文件 2 Fib.c
int Fib(int n) {
    if( n < 2 ) return n == 0 ? 0 : 1;        //边界条件
    else return Fib(n - 1) + Fib(n - 2);  	  //递归表达式
}

在命令行中依次执行下述命令(分别编译后再链接),输出结果为Fibonacci Series:0 1 1 2 3 5 8 13 21 34

# 编译 main.c
gcc -c main.c -o main.o

# 编译 fib.c
gcc -c fib.c -o fib.o

# 链接
gcc main.o fib.o -o fib.exe

# 执行
./fib.exe

注:编译预处理、编译、优化程序、汇编程序这几个阶段统称为编译阶段,而链接阶段是指把链接程序生成可执行文件的过程。