目录
1. 程序的翻译环境和执行环境
第
1
种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第
2
种是执行环境,它用于实际执行码。
1.1 翻译环境(编译链接)
- 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库, 将其需要的函数也链接到程序中。
具体分为以下几个阶段
分离编译,最后链接在一起
(1)预处理(预编译):test.c -> test.i(-E)
- 头文件包含
- 注释的删除(使用空格来替换)
- #define的处理
- 条件编译
(2)编译:test.i -> tedt.s(-S)
- 把C代码转成汇编代码
- 语法、词法、语义、符号分析等
(3)汇编:test.s -> test.o(-c)
- 把汇编代码转成二进制代码(指令),又叫机器码(机器指令)
- 形成符号表
(4)链接:test.o -> test.exe(-l)
- 合并段表
- 符号表的合并和重定位
1.2 运行环境
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
2. 条件编译
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
int i = 0;
int arr[10] = {0};
for(i=0; i<10; i++)
{
arr[i] = i;
//#ifdef __DEBUG__
#if defined(__DEBUG__)//这两句功能一样
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
3. 文件包含
#include
如果是库函数 < >
如果是本地文件 " "
为了避免头文件重复包含,一般有以下两种写法:
#ifndef __TEST_H__
#define __TEST_H__
//中间放 头文件的内容
#endif //__TEST_H__
#pragma once
//下面放头文件的内容;
4. #define 定义标识符
#defifine 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
#define name( parament-list ) stuff
4.1 宏定义
- 不需要加分号,会引起语法错误。正确写法:#define MAX 100
- 所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
//#define SQUARE(x) x*x
//宏是替换,而不是传参
//****加上括号
#define SQUARE(x) ((x)*(x))
int main(){
printf("%d ", SQUARE(5));//5*5
//宏是替换,而不是传参
printf("%d ", SQUARE(5+1));//(5+1)*(5+1);
}
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main(){
int x =2, y = 0;
MAX(x + 2, y + 4);
}
4.2 宏和函数的对比
命名约定:把宏名全部大写,函数名不要全部大写
宏的优势:
- 宏是直接替换的,没有传参和返回参数的开销;
- 宏是类型无关的。函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之宏是直接替换;
- 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
宏的劣势:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。