一.程序的翻译环境和执行环境
翻译环境:源代码转换为可执行的机器指令
执行环境:代码的实际执行
二.详解编译加链接
如图为.c文件在编译和链接过程中如何变成.exe文件的粗略过程。
1.预处理阶段
会进行文本操作,比如将头文件内的内容全部包含显现;完成#define标识符标识的常量的替换,并且删除定义的符号;删除相关注释等。
2.编译过程
自动生成test.s文件,根据语法分析、词法分析、符号汇总、语义分析等把c语言代码转换成汇编代码
3.汇编
自动生成test.o文件,汇编代码转换成二进制指令,形成符号表(全局变量名称、函数名称视为符号,记录符号名称和符号地址
4.链接
合并段表(以Linux环境为例,.o文件、.exe文件都是elf格式即如下图所示),符号表的合并和重定位(合并相同的符号名称并且取其有效地址)
三.预定义符号
__FILE__ 表示进行编译的源代码文件的绝对路径
__LINE__ 表示进行编译的源代码文件的行数
__DATE__ 表示进行编译的源代码文件的日期
__TIME__ 表示进行编译的源代码文件的时间
__STDC__ 如果编译器遵循ANSI C,则其值为1
VS编译器并不遵循ANSI C。
四.执行环境
1.程序的载入
载入形式:a.操作系统自动载入
b.程序员手工安排载入
c.可执行代码置入只读内存的形式载入
2.程序的执行
从main函数进入整个程序
3.执行程序代码
涉及到函数栈帧的知识,之后的博客会更新相关内容
4.终止程序
五.#define
1.#define标识符后并不加;
2.#define定义宏
形式: #define 宏名(参数名) 表达式
注意:参数名左括号紧贴宏名,表达式中参数要加括号,整体也要加括号
3.#define替换规则:在预处理阶段,#define定义的常量、宏、标识符等完成替换,并且要删除原先定义的符号、常量或者宏。
4.# 和 ##
#N 将参数N转换为"N"
N##M 将N和M合并成一个合法的符号
#和##都只在#define定义的语句中使用。
5.带副作用的宏参数
参数中如果出现类似a++这样的参数,结果是难以预料的。
6.宏和函数对比
宏:宏的使用导致代码长度大幅度增长;执行速度更快;由于替换会出现操作符优先级的问题;参数若出现带有副作用的参数,影响结果;参数类型只要符合表达式操作均可,灵活性强,严谨性弱;不可调试;不能递归
函数:函数的多次调用也只使用一块代码区域内的函数;由于函数的调用阶段会发生参数传参、栈帧创建,且函数结束也会消耗时间,所以执行速度慢;不容易出现操作符问题;参数类型要求严格,灵活性差、严谨性强;可调试;能递归
7.命名约定 :宏名全部大写,函数名并不需要全部大写
8.#undef 移除宏定义
9.条件编译
形式一
形式二
形式三 判断标识符是否已经定义
形式四 嵌套指令
10.避免头文件被反复多次调用
方法一:#pragma once
方法二:
11.引用头文件时候<>和""的区别
一般使用头文件
#include<stdio.h> 在引用库目录下的头文件多用<>
#include"test.h" 在引用自己编写的头文件用""
为什么呢?
是因为二者的查找策略不同。
<>的查找策略是直接取库目录下查找。
""的查找策略是1.先去代码的所在路径下查找
2.如果第一步没找到再去库目录下查找
一般建议,库目录下的头文件用<>引,自己编写的头文件用""引。
最后呢,再友情赠送一道百度笔试题!
定义宏的时候,千万要注意括号的使用!!!