“源代码”到“可执行文件”的过程
2014-06-11 23:20
387 查看
由“源代码”到“可执行文件”的过程包括四个步骤:预编译[b]、编译、汇编、链接。[/b]
[b]一、 预编译:[/b]预编译只是对程序的文本起作用,换句话说就是,预编译阶段仅仅对源代码的单词进行变换,而不是对程序中的变量、函数等。
还有一些指令,名称和功能如下表:
在定义宏的时候,有两个运算符:
我们在使用的时候,要加上分号,像我们平时写语句一样。
2. 注意加括号。
在有参数的空定义中,如果含有数值运算,那么就要在“宏整体”和“宏参数”两端都要加上括号。
如:#define max(a, b) ((a)+(b));
3. 注意空格。
在有参数的宏定义中,注意“宏名称”和“参数列表”之间不能有空格。
如:#define max (a, b) ((a)+(b)); 在"max”和”(a, b)”之间不能有空格。
4. 不要使用有副作用的参数区调用宏。
常见的有副作用的参数有:a++,getchar()等。
如:宏定义为#define max (a, b) ((a)+(b)); 那么使用max(i++, j++)调用该宏,会造成 i 或 j 中的一个值增加2,而不是我们期望的 1。
5. 可以使用编译器选项 添加宏 和 移除宏。
我使用的是gcc,添加宏的指令是”-D”,移除宏的指令是”-U”。
6. 宏参数替换的时候,不会替换字符串中的字符。
即不会替换双引号之间的字符,其他的都会被替换,包括单引号之间的。
7. 可以使用#将 宏参数的值 转化为字符串。
直接使用#,是将宏参数的名称转化为字符串。利用下面的技巧(增加一个过渡宏),可以将“宏参数的值”转化为字符串(当宏参数有值时,这时的宏参数常常也是一个宏)。
[cpp] view plaincopyprint?#include <stdio.h> #include <stdlib.h> #define NUMBER ten /* 宏名称为NUMBER,宏的值为ten */ #define Str(x) #x #define XStr(x) Str(x) /* 增加的一个 过渡宏 */ int main(){ printf("Str(NUMBER) == %s /n", Str(NUMBER)); printf("XStr(NUMBER) == %s /n", XStr(NUMBER)); system("pause"); return EXIT_SUCCESS; }
[cpp] view plaincopyprint?Str(NUMBER) == NUMBER XStr(NUMBER) == ten
不过,不建议使用操作符##来连接标识符,因为这个容易是程序可读性大大降低。
目的是为了防止重复包含头文件。如果你查看过gcc或者其他编译器的源代码,你一定对这个非常熟悉。
例如,你要编写一个头文件,myheader.h,那么你的头文件的内容形式应该为:(定义一个_MYHEADER宏)
[cpp] view plaincopyprint?#ifndef _MYHEADER
#define _MYHEADER 1
/* 中间是你的头文件内容 */
#endif /* _MYHEADER */
可以再#include中指定路径来包含文件,例如 #include “../head.h”。但是注意,windows中使用反斜线”/”作为路径分隔符,而Unix系统使用的是斜线”/”。
3. 可以使用 编译器选项 来设置搜索路径。
我使用的gcc,使用的-Idir选项,例如: -I"D:/Dev-Cpp/include"。
2. 在#if中可以使用逻辑操作符(&&、||、!)。在#ifdef 中是不可以使用的,这也是#if的优越点。
[cpp] view plaincopyprint?#include <stdio.h> #include <stdlib.h> #define A 1 #define B 0 int main(){ #if defined( A ) && defined( B ) printf("test logic operation in #if /n"); /* 如果上面的逻辑判断成立,那么将打印出一句话;如果不成立,那么就不会打印这句话 */ #endif system("pause"); return EXIT_SUCCESS; }
[cpp] view plaincopyprint?test logic operation in #if
只要知道“预编译阶段”在真正的“编译阶段”之前,就很容易理解了。预编译阶段只是对组成源代码中的字符进行作用,从某种意义上来说,它有时甚至不知道它的操作对象是什么,它只是按照既定的规则执行替换。
sizeof(int),无论是sizeof的解析,还是类型的解析,都是在“编译阶段”才开始的,编译阶段知道它的操作对象是什么。
下面的代码是错误的
[cpp] view plaincopyprint?#if sizeof(int) == 2 printf("precompile sizeof(int)"); #endif
[b]一、 预编译:[/b]预编译只是对程序的文本起作用,换句话说就是,预编译阶段仅仅对源代码的单词进行变换,而不是对程序中的变量、函数等。
1. 基本内容
预编译指令基本分类如下类别 | 指令 |
预定义符号 | __FILE__、__LINE__、__DATE__、__TIME__、__STDC__ |
宏 | #define |
文件包含 | #include |
条件编译 | #if、#elif、#else、#ifdef、#ifndef、#endif |
指令 | 功能 |
# | 空指令 |
#undef | 移除一个空定义 |
#error | 停止编译,并生成错误信息 |
#line | 修改__LINE__和__FILE__的值 |
#progma | 允许编译器提供额外功能 |
运算符 | 功能 |
# | 将宏参数转换为字符串 |
## | 将多个符号连接成一个标识符 |
2. 宏定义
1. 一般在宏定义的结尾不加分号。我们在使用的时候,要加上分号,像我们平时写语句一样。
2. 注意加括号。
在有参数的空定义中,如果含有数值运算,那么就要在“宏整体”和“宏参数”两端都要加上括号。
如:#define max(a, b) ((a)+(b));
3. 注意空格。
在有参数的宏定义中,注意“宏名称”和“参数列表”之间不能有空格。
如:#define max (a, b) ((a)+(b)); 在"max”和”(a, b)”之间不能有空格。
4. 不要使用有副作用的参数区调用宏。
常见的有副作用的参数有:a++,getchar()等。
如:宏定义为#define max (a, b) ((a)+(b)); 那么使用max(i++, j++)调用该宏,会造成 i 或 j 中的一个值增加2,而不是我们期望的 1。
5. 可以使用编译器选项 添加宏 和 移除宏。
我使用的是gcc,添加宏的指令是”-D”,移除宏的指令是”-U”。
6. 宏参数替换的时候,不会替换字符串中的字符。
即不会替换双引号之间的字符,其他的都会被替换,包括单引号之间的。
7. 可以使用#将 宏参数的值 转化为字符串。
直接使用#,是将宏参数的名称转化为字符串。利用下面的技巧(增加一个过渡宏),可以将“宏参数的值”转化为字符串(当宏参数有值时,这时的宏参数常常也是一个宏)。
[cpp] view plaincopyprint?#include <stdio.h> #include <stdlib.h> #define NUMBER ten /* 宏名称为NUMBER,宏的值为ten */ #define Str(x) #x #define XStr(x) Str(x) /* 增加的一个 过渡宏 */ int main(){ printf("Str(NUMBER) == %s /n", Str(NUMBER)); printf("XStr(NUMBER) == %s /n", XStr(NUMBER)); system("pause"); return EXIT_SUCCESS; }
#include <stdio.h> #include <stdlib.h> #define NUMBER ten /* 宏名称为NUMBER,宏的值为ten */ #define Str(x) #x #define XStr(x) Str(x) /* 增加的一个 过渡宏 */ int main(){ printf("Str(NUMBER) == %s /n", Str(NUMBER)); printf("XStr(NUMBER) == %s /n", XStr(NUMBER)); system("pause"); return EXIT_SUCCESS; }输出结果为:
[cpp] view plaincopyprint?Str(NUMBER) == NUMBER XStr(NUMBER) == ten
Str(NUMBER) == NUMBER XStr(NUMBER) == ten8. 使用##运算符来实现标识符连接。
不过,不建议使用操作符##来连接标识符,因为这个容易是程序可读性大大降低。
3. 文件包含
1. 要将头文件的定义在保护条件中。目的是为了防止重复包含头文件。如果你查看过gcc或者其他编译器的源代码,你一定对这个非常熟悉。
例如,你要编写一个头文件,myheader.h,那么你的头文件的内容形式应该为:(定义一个_MYHEADER宏)
[cpp] view plaincopyprint?#ifndef _MYHEADER
#define _MYHEADER 1
/* 中间是你的头文件内容 */
#endif /* _MYHEADER */
#ifndef _MYHEADER #define _MYHEADER 1 /* 中间是你的头文件内容 */ #endif /* _MYHEADER */2. 注意windows系统和Unix系统的路径符号不同。
可以再#include中指定路径来包含文件,例如 #include “../head.h”。但是注意,windows中使用反斜线”/”作为路径分隔符,而Unix系统使用的是斜线”/”。
3. 可以使用 编译器选项 来设置搜索路径。
我使用的gcc,使用的-Idir选项,例如: -I"D:/Dev-Cpp/include"。
4. 条件编译
1. #ifdef等价于#if defined(),#ifndef等价于#if !defined()。2. 在#if中可以使用逻辑操作符(&&、||、!)。在#ifdef 中是不可以使用的,这也是#if的优越点。
[cpp] view plaincopyprint?#include <stdio.h> #include <stdlib.h> #define A 1 #define B 0 int main(){ #if defined( A ) && defined( B ) printf("test logic operation in #if /n"); /* 如果上面的逻辑判断成立,那么将打印出一句话;如果不成立,那么就不会打印这句话 */ #endif system("pause"); return EXIT_SUCCESS; }
#include <stdio.h> #include <stdlib.h> #define A 1 #define B 0 int main(){ #if defined( A ) && defined( B ) printf("test logic operation in #if /n"); /* 如果上面的逻辑判断成立,那么将打印出一句话;如果不成立,那么就不会打印这句话 */ #endif system("pause"); return EXIT_SUCCESS; }运行结果:
[cpp] view plaincopyprint?test logic operation in #if
test logic operation in #if3. sizeof(int)在预编译阶段是不会被求值的。
只要知道“预编译阶段”在真正的“编译阶段”之前,就很容易理解了。预编译阶段只是对组成源代码中的字符进行作用,从某种意义上来说,它有时甚至不知道它的操作对象是什么,它只是按照既定的规则执行替换。
sizeof(int),无论是sizeof的解析,还是类型的解析,都是在“编译阶段”才开始的,编译阶段知道它的操作对象是什么。
下面的代码是错误的
[cpp] view plaincopyprint?#if sizeof(int) == 2 printf("precompile sizeof(int)"); #endif
#if sizeof(int) == 2 printf("precompile sizeof(int)"); #endif
5. 额外注意
把一个预处理指令写成多行的形式,要使用符号”\”,并且在该符号后面应紧跟换行符。而非预处理指令的代码行不需要使用该符号,直接换行即可。 原因:编译阶段会自动忽略空白符,而预编译阶段不会。相关文章推荐
- c源代码到可执行文件的过程
- c语言从一个源代码文件到生成可执行文件的过程
- 从C源代码到可执行文件的总体过程框架
- 分析源代码编译链接过程和shell中ELF格式可执行文件的初始化执行过程
- 浅谈从源代码文件到二进制可执行文件的过程
- c源代码到可执行文件的过程
- 浅谈从源代码文件到二进制可执行文件的过程
- 源代码文件到二进制可执行文件的过程
- 从C源代码到可执行文件的总体过程框架
- 浅谈从源代码文件到二进制可执行文件的过程
- 从源代码到可执行文件——编译全过程解析
- 从源代码到可执行文件的过程
- 从源代码到可执行文件
- 文件系统执行过程【草稿】
- [zz]登录Linux时/etc/profile、~/.bash_profile等几个文件的执行过程
- [转]linux中,/etc/profile、~/.bash_profile等几个文件的执行过程
- 关于登录linux时,/etc/profile、~/.bash_profile等几个文件的执行过程。
- 登录Linux时/etc/profile、~/.bash_profile等几个文件的执行过程
- 将java项目生成exe可执行文件全过程
- 登录Linux时/etc/profile、~/.bash_profile等几个文件的执行过程