C语言编译过程
最近准备秋招,想把知识体系整理下,可以更好的应对面试中的问题。
预处理
命令:gcc -E add.c -o add.i
C 预处理器相当于一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。或者说是扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器,这一过程中不会进行语法解析。
可以打开.i
文件看一下,多出来很多行,但还是c语言的东西。
宏定义指令
#define指令
语法:
#define NUM 10
int arrar[NUM];
好处:
- 习惯上使用大写字母来定义宏,使用方便。
- 易读性高。
- 容易修改。
注意:宏展开需要注意运算符优先级的问题。
带参数的#define指令(带参宏)
带参宏的使用与函数类似
#define RGB888(r, g, b) (((r&0xff) << 16) | ((g&0xff) << 8) | (b & 0xff))
使用时同样需要注意优先级问题,使用时最好加上括号。
带参宏和带参函数的区别
宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
宏没有返回值,函数可以有返回值
#运算符
也被称为字符串化运算符,即
#define STRING(n) "123"#n
int main()
{
printf("%s\n", STRING(567));
return 0;
}
//输出:123567
##运算符(很少用)
##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。
#define NUM(a,b,c) a##b##c
#define STR(a,b,c) a##b##c
int main()
{
printf("%d\n",NUM(1,2,3));
printf("%s\n",STR("aa","bb","cc"));
return 0;
}
/*
输出:
123
aabbcc
*/
条件编译
贴上本人常用的条件编译。
//1.防止头文件被重复包含出现意想不到的问题
#ifndef __HEAD_H__
#define __HEAD_H__
#endif
//2.条件编译屏蔽代码,在调试中常用
#if 0
#endif
//3.代码条件定义,来自linux内核
#ifdef KEY_DEBUGGING
unsigned magic;
#define KEY_DEBUG_MAGIC 0x18273645u
#define KEY_DEBUG_MAGIC_X 0xf8e9dacbu
#endif
头文件包含
#include <stdio.h>
#include "head.h"
//<> 告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件
//"" 告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头
//文件,如果找不到,再搜索编译器自带的头文件
编译
命令:gcc -S add.i -o add.s
将经过预处理之后的程序转换成特定汇编代码的过程,生成的.s
文件是汇编文件
汇编
命令:gcc -c add.s -o a.o
将上一步的汇编代码转换成机器码,生成的.o
文件是二进制文件
链接
命令:gcc add.o -c add
将所有二进制形式的目标文件和系统组件组合成一个可执行文件。
面试预处理可能会问一些,其余的知道就行了。