程序的翻译环境和执行环境
第一种是翻译环境,在这个环境中源代码被转换成可执行的机器指令。
第二种是执行环境,执行实际的代码。
1 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
2 每个目标文件由连接器(linker)捆绑在一起,形成一个单一二完整的可执行程序。
3 链接器同时也会引入标准c函数库中任何被该程序所引用到的函数,而且它还可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
编译(编译器)也分为3阶段(文本操作):
1 预处理(*.c): #include 头文件的包含 生成(.i文件)
2 编译(*.i): 注释删除 ,用空格替换注释 生成(.s文件)
3 汇编(*.s): 替换#define 语法分析(形成符号表) 词法分析 语义分析 符号汇总(把c语言代码翻译成汇编代码,最后将汇编代码转换成二进制代码 ,生成(.obj文件))
符号表:
链接(链接器)分为2阶段:
1 合并段表:不同符号表之间的段按照一定的规则合并,生成exe可执行文件。
2 符号表的合并和重定位
合并后:
执行时链接器会按照合并后的地址去找,对应的函数运行,如果没有,或者合并失败,就是链接错误。
运行环境:
1 程序必须载入内存中。
在有操作系统的内存中,一般由操作系统完成。在独立的环境中,程序的载入必须手动操作,或者是通过可执行代码置入只读内存来完成。
2 程序的执行便开始,接着便开始调用main函数。
3 执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程中一直保留他们的值。
4 结束程序。 正常终止main函数,或者意外终止。
预处理详解:
预定义符号(#):
//预定义符号
int main()
{
printf("%s\n", __FILE__);//进行编译的源文件的绝对路径
printf("%d\n", __LINE__);//显示当前代码的行数
printf("%s\n", __DATE__);//显示当前的日期
printf("%s\n", __TIME__);//显示当前的时间
//写日志文件
int i = 0;
int arr[10] = {0};
FILE* pf = fopen("text.txt", "w");
for (i = 0; i < 10; i++)
{
arr[i] = i;
fprintf(pf, "file:%s line:%d date:%s time:%s i=%d\n",
__FILE__, __LINE__, __DATE__, __TIME__, i);
printf("%d ", arr[i]);
}
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
日志文件:
#define
#define 可以定义标识符
#define MAX 100
#define STR "hehe"
#define reg register
#define do_forever for(;;)
int main()
{
//reg int a == register int a;
//do_forever;//死循环
int max = MAX;
printf("%d\n", max);//100
printf("%s\n", STR);//hehe
return 0;
}
#define 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常叫宏(macro)或者定义宏(define macro)
声明:
#define name(parament_list) stuff
//其中的parament-list是一个由逗号隔开的符号表,他们可能出现在stuff中
注意:参数列表的左括号必须与name紧练,否则会被解释为stuff的一部分。
#define SQUARE(X) X*X
int main()
{
int ret = SQUARE(5);
printf("ret = %d\n", ret);//25
return 0;
}
注意:宏的参数是进行替换的,不是传参
#define SQUARE(X) X*X
int main()
{
int ret = SQUARE(5+1);
printf("%d\n", ret);//这里传过去的是5+1*5+1 = 11
return 0;
}
改进:
#define SQUARE(X) (X)*(X)
int main()
{
int ret = SQUARE(5 + 1);
printf("%d\n", ret);//这里传过去的是(5+1)*(5+1) = 36
return 0;
}
举例1:
#define DOUBLE(X) X+X
int main()
{
int a = 5;
int ret = 10 * DOUBLE(a);//10 * 5+5=55
printf("%d\n", ret);
return 0;
}
举例2:
#define MAX(X, Y) (X)>(Y)?(X):(Y)
int main()
{
int a = 10;
int b = 11;
//int max = ((a++)>(b++)?(a++):(b++));
int max = MAX(a++, b++);
printf("%d %d %d\n", max, a, b);//12 11 13
return 0;
}
记住:宏的参数是替换,不是计算好后带进去
先执行(a++)>(b++),a为11,b为12,
再执行(a++)>(b++),其中(a++)不执行,(b++)的值先赋给max,然后b再++为13
举例3:
宏和函数表示
#define MALLOC(num, type) (type*)malloc(num*sizeof(type))
int main()
{
//函数表示
int* p = (int*)malloc(10*sizeof(int));
//宏表示
int* p = MALLOC(10, int);
//int* p = (int*)malloc(10 * sizeof(int));
return 0;
}
(#和##)
如何把参数插入到字符串中?
#define PRINT(X) printf("the value of "#X" is %d\n", X)
int main()
{
int a = 10;
int b = 20;
PRINT(a);
//printf("the value of ""a"" is %d\n", a);
PRINT(b);
//printf("the value of ""b"" is %d\n", b);
return 0;
}
##作用:##可以把位于它两端的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。
#define CAT(X, Y) X##Y
int main()
{
int Class101 = 2019;
printf("%d\n", CAT(Class, 101));
//printf("%d\n", Class##101);
//printf("%d\n", class101);
return 0;
}
宏和函数的对比总结
命名约定:把宏名全部大写,函数名不要全部大写。
#undef: 移除一个宏定义
#undef NAME
如果现存的一个名字需要被重新定义,那么他的旧名字首先要被移除。
条件编译
在编译一个程序的时候我们如果要将一个语句(一组语句)编译或者放弃的是很方便的,因为我们有条件编译指令。
例如:调试性代码。删除可惜,保留又碍事,所以我们可以选择性的编译。
#define DEBUG
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for (i = 0; i < 10; i++)
{
arr[i] = 0;
#ifdef DEBUG
printf("%d ", arr[i]);
#endif
}
return 0;
}
上述代码。如果删除#define DEBUG,则不会打印arr[i],如果前面定义了DEBUG,则后面会打印arr[i]
常见的条件编译指令:
1. #if 常量表达式
//...
#endif
//常量表达式由预处理器求值
2. 多分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
int main()
{
#if 1==2
printf("haha\n");
#elif 2==2
printf("hehe\n");
#else
printf("xixi\n");
#endif
return 0;
}
3. 判断是否被定义
#if defined(symbol)
#ifdef symbol
//...
#if !define(symbol)
#ifnedf symbol
#define DEBUG 0
int main()
{
//#if defined(DEBUG)
// printf("hehe\n");//hehe
//#endif
#if !defined(DEBUG)
printf("hehe\n");//不打印
#endif
return 0;
}
4. 嵌套指令
#if defined(OS_NUIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
嵌套文件包含:
comm.h和comm.c是公共模块。bbbb.h和bbbb.c使用了公共模块,dddd.h和dddd.c使用了公共模块,aaaa.c和aaaa.h使用了bbbb.h和dddd.h模块,这样就会最终程序中就会出现两份comm.h的内容,这样就造成了文件的重复。
如何解决这个问题?答案:条件编译
每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
或者:
#pragma once
就可以避免头文件的重复引用。