C语言编译过程
gcc -E hello.c -o hello.i 预处理
gcc -S hello.i -o hello.s 编译
gcc -c hello.s -o hello.o 汇编
gcc hello.o -o hello hello_elf 链接
预处理:头文件包含\宏替换\条件编译\删除注释\不做语法检查
编译:将预处理后的文件生成汇编文件 语法检查
汇编:将汇编文件 编译 二进制文件
链接:将众多的二进制文件+库+启动代码 生成 可执行文件
总结:一步到位 编译 gcc 源文件 -o 可执行文件
预处理
C编译器提供的预处理功能主要有以下四种:
- 文件包含 #include
- 宏定义 #define
- 条件编译 #if #endif ..
- 一些特殊作用的预定义宏
头文件包含<>,"" (了解)
#include<stdio.h> 表示从系统指定的目录下寻找stdio.h
#include"hehe.h" 表示先源文件所在的目录寻找,如果找不到再到系统指定的目录下寻找
宏定义 define
在源程序中,允许一个标识符(宏名)来表示一个语言符号字符串用指定的符号代替指定的信息
宏一般为 大写(将宏和普通变量区分开)
1.不带参数的宏
#define 宏名 字符串
例如:define PI 3.1415926
- 在编译预处理时,将程序中在该语句以后出现的所有的PI都用3.1415926代替
- 这种方法使用户能以一个简单的名字代替一个长的字符串
- 在预编译时将宏名替换成字符串的过程称为"宏展开"
- 宏定义,只在宏定义的文件中起作用
#include<stdio.h>
//宏 后面不要加分号
#define N "hehe"
void test01()
{
//在预处理阶段 "hehe" 替换 代码中所有出现的N (宏展开)
printf("%s\n",N);
}
int main(int argc,char *argv[])
{
test01();
return 0;
}
中止宏的作用范围: #undef 宏名
#include<stdio.h>
#define N "hehe"
void test01()
{
printf("%s\n",N); //OK 识别的
//中止宏N的作用
#undef N
//printf("%s\n",N); err 不识别N
return;
}
int main(int argc,char *argv[])
{
test01();
return 0;
}
2.带参数的宏(宏函数)
格式:#define 宏名(形参表) 字符串
调用:宏名(形参表)
宏展开:进行宏替换
#define 宏名(参数1,参数2,....) 字符串
//宏的参数 a b 不能类型
//#define MY_ADD(int a,int b) a+b //错误
#define MY_ADD(a,b) a+b
//调用 宏名(参数)
MY_ADD(10,20); // 等价 10+20
[b]案例:
[pre]#include<stdio.h>
//宏展开的本质 就是替换
#define MY_MUL1(a,b) a*b
#define MY_MUL2(a,b) ((a)*(b))
void test01()
{
printf("%d\n",MY_MUL1(10,20)); // MY_MUL(10,20) = 10*20
//MY_MUL1(10+10,20+20) == 10+10*20+20
printf("%d\n",MY_MUL1(10+10,20+20)); //输出:230 ,不能保证完整性
//#define MY_MUL2(a,b) ((a)*(b)) == ((10+10)*(20+20))
printf("%d\n",MY_MUL2(10+10,20+20)); //输出:800 ,保证完整性
return;
}
int main(int argc,char *argv[])
{
test01();
return 0;
}
3.带参数的宏(宏函数) 和普通函数有啥区别
带参数的宏(宏函数) 调用多少次 就会展开多少次,执行代码的时候 没有函数调用的过程,也不需要函数的出入栈的问题,所以带参数的宏 浪费空间 节省了时间。
带参的函数:代码只有一份,存在代码段,调用的时候去代码段读取函数指令,调用的时候要压栈(保存调用函数前的相关信息),调用完出栈(恢复调用函数前的相关信息),所以函数浪费了时间,节省了空间。
#include<stdio.h>
//宏展开的本质 就是替换
//宏函数
#define MY_MUL1(a,b) a*b
#define MY_MUL2(a,b) ((a)*(b))
//普通函数
int my_mul(int a,int b) //a = 10+10 b = 20+20
{
return a*b;
}
void test01()
{
//预处理阶段完成 宏的替换
printf("%d\n",MY_MUL1(10,20)); // MY_MUL(10,20) = 10*20
//MY_MUL1(10+10,20+20) == 10+10*20+20
printf("%d\n",MY_MUL1(10+10,20+20)); //输出:230 ,不能保证完整性
//#define MY_MUL2(a,b) ((a)*(b)) == ((10+10)*(20+20))
printf("%d\n",MY_MUL2(10+10,20+20)); //输出:800 ,保证完整性
//执行
printf("%d\n",my_mul(10+10,20+20)); //输出:800
return;
}
int main(int argc,char *argv[])
{
test01();
return 0;
}
案例,最终输出结果是多少?:
#define MY_ADD(a,b) a+b
#define MY_MUL(a,b) a*b
printf("%d\n", MY_MUL(MY_ADD(10+10,20+20),MY_MUL(10+10,20+20)) );
解析:
#include<stdio.h>
#define MY_ADD(a,b) a+b
#define MY_MUL(a,b) a*b
int main(int argc,char *argv[])
{
//MY_MUL(MY_ADD(10+10,20+20),MY_MUL(10+10,20+20))
//MY_MUL(10+10+20+20,10+10*20+20)
//10+10+20+20*10+10*20+20 = 460
printf("%d\n", MY_MUL(MY_ADD(10+10,20+20),MY_MUL(10+10,20+20)) );
return 0;
}
条件编译:
一般情况下,源程序中所有的行都参加编译,但有时希望对部分程序行只在满足一定条件时才编译,技对着部分源程序行指定编译条件
测试存在:
#ifdef 标识符
程序段1;
#else
程序段2;
#endif
测试不存在:
#ifndef 标识符
程序段1;
#else
程序段2;
#endif
判断条件是否成立:
#if 标识符
程序段1;
#else
程序段2;
#endif
案例一:测试不存在
运行结果:
案例二:
#include<stdio.h>
int main(int argc,char *argv[])
{
#if 1
printf("--001--\n");
#else
printf("--002--\n");
#endif
return 0;
}
综合案例:
通过条件编译 控制大小写的转换
#include<stdio.h>
#include<string.h>
int main(int argc,char *argv[])
{
char buf[128] = "";
int i = 0;
printf("请输入字符串:");
//fgets会获取换行符“\n”
fgets(buf,sizeof(buf),stdin);
//去掉换行符 strlen返回的是字符串的长度 不包含'\0'
//strlen(buf)-1 这是换行符的下标位置
buf[strlen(buf)-1] = 0;
while(buf) //最后一个元素是'\0' == 0 == 假 循环进不去
{#if 0
//大写变小写
if(buf>='a' && buf <= 'z')
buf =buf-32;
#else
//小写变大写
if(buf>='A' && buf <= 'Z')
buf =buf+32;
#endif
i++;
}
printf("buf = %s\n",buf);
return 0;
}
|