目录
一、C语言编译过程
c语言编译过程:
预处理、编译、汇编、链接
1.1 预处理
gcc -E hello.c -o hell.i
将 .c 中的头文件展开、宏展开
生成的文件是 .i 文件
注意:
- 预处理阶段只是对include等预处理操作进行处理并不会进行语法检查,这个阶段有语法错误也不会报错,第二阶段即编译阶段才会进行语法检查
1.2 编译
gcc -S hello.i -o hello.s
将预处理之后的 .i 文件生成 .s 汇编文件
1.3 汇编
gcc -c hello.s -o hello.o
将 .s 汇编文件生成 .o 目标文件
1.4 链接
gcc hello.o -o hello_elf
将 .o 文件链接成目标文件
二、宏定义
宏是在预编译时候进行替换的
2.1 不带参数的宏定义
作用范围:从定义的地方到本文件的末尾
#define PI 3.14159
一般用大写字母表示,以便与变量名相区分
#define SIDE 5
#define PERIMETER 4 * SIDE
#define AREA SIDE * SIDE
#define STAND "you are girl"
printf(STAND);
//宏定义的好处:只要改变了定义是的常量表达式,则代码中只要是使用该宏定义的 位置都会改变
#define PI 3.14
void main()
{
printf("PI = %lf\n", PI);
double d;
d = PI;
printf("d = %lf\n", d);
}
如果想在中间中止宏定义范围:
#undef PI//中止PI的作用
🌭注意点:
- 如果在串中含有宏名,则不进行替换
#define PI 3.14
void main()
{
char xx[10] = "hello ,PI";
printf("%s\n", xx);
}
- 如果串长于一行,可在该行末尾用‘ \ ’续行。
- #define 命令出现在程序中函数外面,宏名有效范围定义命令之后到此源文件结束。
- 用#undef 命令终止宏定义的作用域
#define TEST "this is an example"
main()
{
printf(TEST);
printf("\n");
#undef TEST
}
宏的好处:需要修改宏定义,其他地方在预编译的时候就会重新替换
2.2 带参数的宏定义
#define MIX(a,b) ((a) * (b) + (b))
main()
{
int x = 5, y = 9;
printf("%d, %d\n", x, y);
printf("the min number %d\n", MIX(x, y));
printf("\n");
}
#include <stdio.h>
//带参宏,类似一个简单的函数,将函数的参数进行设置
//#define S(a, b) a * b
//修改:
#define S(a, b) ((a) * (b))
void main()
{
printf(" %d\n", S(2, 4));
//注意:宏定义只是简单的替换, 不会自动加括号
//2 + 8 * 4 //乘法优先级比加法高
//带括号: (2+8)*4
printf(" %d\n", S(2 + 8, 4));
}
注意点:
- 宏定义时参数要加括号。
- 宏扩展使用括号来保护表达式低优先级的操作,以便调用时达到想要的效果。
- 带参的宏的形参没有类型名
2.3 带参宏与带参函数区别
- 带参宏被调用多少次就会展开多少次,执行代码时没有函数调用的过程,不需要压栈弹栈,所以带参宏,是浪费了空间,因为被展开多次,节省时间。
- 带参函数,代码只有一份,存在代码段,调用的时候去代码段取指令,调用时候,压栈弹栈,有个调用的过程,所以说,带参函数节省空间,浪费时间,
- 带参函数的形参是有类型的,带参宏的形参是没有类型的
- 如果功能实现代码相对简单,并不需要开辟太多空间,可以选择使用带参宏,但大多数情况都会使用函数。
三、#include 指令
#include可以将另外一个源文件全部内容包含进来,
#include <stdio.h> //系统指定的路径下找的头文件
#include "studio.h" //先在当前目录下找头文件,找不到,再到系统指定路径下找
- 尖括号:系统到存放C库函数头文件所在的目录中寻找要包含的文件,即标准方式。如:为调用库函数包含的头文件
- 双引号:系统先在用户当前目录中寻找要包含的文件,若找不到,再存放C库函数头文件所在的目录中寻找要包含的文件.如:用户自己编写的文件
存在将文件嵌入#include命令中,嵌套的嵌入文件。
文件f1.h
#define P printf
#define S scanf
#define D "%d"
#define C "%c"
文件f2.c
#include <stdio.h>
#include "f1.h"
void main()
{
int a;
P("请输入第一个整数值:\n");
S(D, &a);
}
一般情况将以下内容放在 . h中
- 宏定义
- 结构、联合和枚举声明
- typedef声明
- 外部函数声明
- 全局变量声明
注意点:
- 一个#include 命令只能指定一个被包含的文件
- 😊include也可以包含.c文件,但不要包含,因为include包含的文件会在预处理编译被展开,如果一个.c被包含多次,展开多次,会导致函数重复定义。所以不要包含.c文件
- 😊预处理阶段只是对include等预处理操作进行处理并不会进行语法检查,这个阶段有语法错误也不会报错,第二阶段即编译阶段才会进行语法检查
- 文件包含可以嵌套,即一个被包含的文件中还可以包含另一个被包含文件
- 当在filel.c中包含文件file2.h时,那么在预编译后就成为一个文件而不是两个文件,这时如果file2.h中有全局静态变量,则该全局变量在filel.c文件中也有效,这时不需要再用extern声明。
四、选择性(条件)编译
4.1 #if 命令 、#else命令和elif命令
一般用于注释多行代码
#if 0
...
#endif
①、#if 命令
#if 命令:
如果 #if 指令后的参数表达式为真,则编译 #if 到 #endif之间的程序段,
否则跳过这段程序。#endif用来表示#if段的结束。
形式:
#define NUM 50
main()
{
int i = 0;
#if NUM > 50
i++;
#endif
#if NUM == 50
i = i + 50;
#endif
#if NUM < 50
i--;
#endif
printf(" %d\n", i);
printf("\n");
}
②、#else命令
如果表达式为真,编译第一段代码,否则编译第二段代码
#else :为#if为假时提供另一种选择,
#define NUM 50
main()
{
int i = 0;
#if NUM > 50
i++;
#else
#if NUM < 50
i--;
#else
i = i + 50;
#endif
#endif
printf(" %d\n", i);
printf("\n");
}
③、#elif命令
#elif命令: 用来建立“如果 。。。 或者如果。。。”
#define NUM 50
main()
{
int i = 0;
#if NUM > 50
i++;
#elif NUM < 50
i--;
#else
i = i + 50;
#endif
printf(" %d\n", i);
printf("\n");
}
4.2 #ifdef 命令 及 #ifndef命令
#ifdef :如果有定义
- #ifdef: 如果宏替换名已经被定义,则对“语句段”进行编译;如果没有定义#ifdef后面的宏替换名。则不执行。
- #ifdef 与 #else连用:
如果宏替换名已被定义过,则对“语句段1”进行编译;
如果没有定义#ifdef后面的宏替换名,则对“语句段2”进行编译。
#ifndef:如果无定义
- #ifndef: 如果宏替换名未被定义,则对“语句段”进行编译;如果定义#ifndef后面的宏替换名。
- #ifndef与#else连用:
如果宏替换名未被定义过,则对“语句段1”进行编译;
如果定义#ifndef后面的宏替换名,则对“语句段2”进行编译。
#ifndef 常用在防止头文件重复包含的情况。常用于多文件编程中.h的第一行是#ifndef,最后一行是#endif
#define STR "dillgence is the parent of success\n"
main()
{
#ifdef STR
printf(STR);
#else
printf("dillgence is the root of all evil\n");
#endif
printf("\n");
#ifndef ABC
printf("dillgence is the root of all evil\n");
#else
printf(STR);
#endif
printf("\n");
}
4.3 #undef命令
#undef 可以删除事先定义的宏定义。
#define MAX_SIZE 100
char array[MAX_SIZE];
#undef MAX_SIZE
4.4 #line命令
#line 用于改变__LINE__与__FILE__的内容,__LINE__存放当前编译行的行号,__FILE__存放当前编译的文件名
行号为源程序中当前行号,文件名为源文件的名字,命令#line主要用于调试及其他特殊应用。
#line 100 "15.7.C"
main()
{
printf("1.当前行号:%d\n", __LINE__);
printf("2.当前行号:%d\n", __LINE__);
printf("\n");
}
4.5 #pragma命令
#pragma:设定编译器的状态,或指示编译器完成一些特定动作。
- message参数:该参数能够编译信息输出窗口中输出相应的信息。
- code_seg参数:设置程序中函数代码存放的代码段
- code参数:保证头文件被编译一次。
预定义宏名
- __LINE__:当前被编译代号的行号。
- __FILE__:当前源程序的文件名称。
- __DATE__:当前源程序的创建时间
- __TIME__:当前源程序的创建时间
- __STDC__:用来判断当前编译器是否为C标准,其值为1,表示符合标准C,否则不是标准C
选择性编译都是在预处理阶段干的事情。