1.程序的翻译环境
在这个环境中源代码被转换为可执行的机器指令。
2.程序的执行环境
它用于实际执行代码。
3.详解:C语言程序的编译 + 链接
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库, 将其需要的函数也链接到程序中。
1 预处理 选项 gcc -E test.c -o test.i 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件 中。
2. 编译 选项 gcc -S test.c 编译完成之后就停下来,结果保存在test.s中。
3. 汇编 gcc -c test.c 汇编完成之后就停下来,结果保存在test.o中。
test.c- - - - >test.i - - - -> test.s - - - - >test.o在经过连接转换为test.exe文件
这个过程就是预编译、编译、和汇编加链接处理文件的过程。
在预编译/预处理过程中会:
①#include头文件的包含
②注释删除(使用空格来替换注释)
③#define(这里已经开始替换了)
④test.c - - - - test.i
编译:
①test.i – - - test.s把c代码翻译成为汇编代码
②语法,词法,语义的分析,符号汇总
4.预定义符号介绍
这些预定义符号都是语言内置的
int main()
{
printf("%s\n",__FILE__);//进行编译的源文件 (当前文件的绝对路径)
printf("%d\n", __LINE__); //文件当前的行号
printf("%s\n", __DATE__); //文件被编译的日期
printf("%s\n",__TIME__); //文件被编译的时间
printf("%s\n",__FUNCTION__);//主函数名
//__STDC__; //如果编译器遵循ANSIC,其值为1,否则未定义,对于傲娇的VS编译器是不能识别这个预处理符号的。
return 0;
}
5.预处理指令 #define
#define 定义标识符
#define MAX 100
#define STR "hehe" // 定义字符串
#define reg register//为register这个关键字,创建一个简短的名字
提问:
在define定义标识符的时候,要不要在后加上;?
答案:不需要添加’;’,因为一旦你那次没有写,那就回报错,所以为了避免这种事情发生,统一的最好不要加’;’。
6.宏和函数的对比
#define SQUARE(X) X*X
int main()
{
int ret = SQUARE(5); // 宏气的作用是替换,把X替换为5,然后作后面的5*5运算
int ret = SQUARE(5 + 1);// 5+1*5+1 = 5+5+1 = 11
return 0;
}
宏的作用是”替换”,一般的约定俗成,在写宏的时候都会写为大写。为了避免一些表达式的结果和预想的不一样,最好在写宏的时候可以给其加上()。
例如
#define SQUARE(X) (X)*(X)
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
① 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
②当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
7.预处理操作符#和##的介绍
如何把参数插入到字符串中?
错误写法
#define PRINT(X) printf("the value of X is %d\n",X)
int main()
{
int a = 10;
int b = 20;
PRINT(a); // the value of X is 10
PRINT(b); // the value of X is 20
//结果是错误的,我希望X的位置可以被替换成'a' 和 'b'
return 0;
}
正确写法引入‘#’,相当于把这个字符转换成对应的字符串了,并且对于字符串来说,他会默认的把字符串都连接在一起(字符串是有自动连接的特点的。
)
#define PRINT(X) printf("the value of "#X" is %d\n",X)
int main()
{
int a = 10;
int b = 20;
PRINT(a); //the value of a is 10
PRINT(b); // the value of b is 20
return 0;
}
##的作用
##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
#define CAT(X,Y) X##Y
int main()
{
int Class84 = 2019;
printf("%d\n", CAT(Class, 84));
//printf("%d\n",Class##84);
//printf("%d\n",Class84);
//所以结果是 2019
return 0;
提示:宏有时也是具有副作用的(不要轻易的在宏里面写++或–符号)
计算一下结果?
#define MAX(X,Y) (X)>(Y)?(X):(Y)
int main()
{
int a = 10;
int b = 11;
int max = MAX(a++, b++);
//int max = ((a++)>(b++)?(a++):(b++));//再次声明完全替换
//第一个表达式是先使用后++。然后a和b的值就都改变了,到后面的b++这里的b=12,使用完就是13
printf("%d\n", max); //12
printf("%d\n", a);//11
printf("%d\n", b);//13
return 0;
}
宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#define MAX(a, b) ((a)>(b)?(a):(b))
int max(int x,int y)
{
return (x>y?x:y);
}
那为什么不用函数来完成这个任务? 原因有二:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序 的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可 以适用于整形、长整型、浮点型等可以用来比较类型。宏是类型无关的。
当然和宏相比函数也有劣势的地方:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
宏是可以传类型的!
#define SIZEOF(type) sizeof(type)
int main()
{
int ret = SIZEOF(int);
//int ret = sizeof(int);
printf("%d\n", ret);
return 0;
}
#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;
}
命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是: 把宏名全部大写 函数名不要全部大写。
#undef
这条指令用于移除一个宏定义。
8.条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
#include <stdio.h>
#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 //如果定义了DUBUG 那就执行下面的语句
//#if defined(DEBUG) 和上面的意思是一样的。
//#ifndef DEBUG 相反的意思,如果没有定义,那就执行下一条语句
printf("%d ",arr[i]);
#endif
#ifdef 1 // 如果是1那就是真,执行下面的语句,为0是假,不执行下面的语句
printf("%d ",arr[i]);
#endif
}
return0;
}
多个分支的条件编译
int main()
{
#if 1==2
printf("haha\n");
#elif 1==2
printf("hehe\n");
#else
printf("heihei\n");
#endif
return 0;
}
嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif #ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
9.预处理指令#include
包含库文件"<>"
包含的是本地文件(自定义文件)""来包含,
查找策略:
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。
有时候难免多次包含同一个头文件,就会多次的进行调用,增加程序运行的时间,为了能更好的避免这个问题,可以在头文件处写
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif
或者
#pragma once
就可以避免头文件的重复引入。
预处理的名称