东莞购物网站,网站建设的经营范围,免费推广平台哪个好,设计网站大全有哪些目录
一、翻译环境
编译
1.预编译#xff08;预处理#xff09;
2.编译
3.汇编
链接
二、运行环境
三、预处理符号详解
1.预定义符号
2.#define
3.#undef
4..命令行定义
5.条件编译
6.头文件包含 代码是怎么变成可执行程序的#xff1f; 一、翻译环境 翻译环境…目录
一、翻译环境
编译
1.预编译预处理
2.编译
3.汇编
链接
二、运行环境
三、预处理符号详解
1.预定义符号
2.#define
3.#undef
4..命令行定义
5.条件编译
6.头文件包含 代码是怎么变成可执行程序的 一、翻译环境 翻译环境是将.c文件翻译成.exe文件而执行环境是将.exe文件执行成代码。翻译环境又分为编译和链接两部分。
我们假设当前撰写的文件为test.c文件 编译
编译整个过程完成的事情生成可执行程序依赖编译器如VS2022 1.预编译预处理
预处理阶段也成为文本操作也就是处理完成之后我们还可以看得懂的。
【这个步骤做的事情】
1test.c文件会被处理成test.i文件。
2注释会被替换成一个空格可以理解成被删除了。
3头文件包含的文件会完整的展开到文件里面#include包含的文件会消失
4#define符号的替换 所有的预处理指令都会在预编译阶段被处理掉 2.编译
生成.s文件
把c语言代码生成汇编代码
【编译步骤会完成的事情】
1test.i文件会变成test.s文件也就是生成test.s文件
2把C语言代码翻译成汇编代码肉眼看不懂 生成汇编代码需要做的事情 1.词法分析 2.语法分析 3.语义分析 4.符号汇总 3.汇编
把汇编代码生成了二进制的指令生成.o的文件目标文件
生成符号表
【汇编操作会完成的事情】
在linux环境下生成的目标文件是.o文件在windows环境下生成的是.obj文件
1test.s文件会变成test.o文件也就是产生test.o文件--目标文件
2把汇编代码翻译成了二进制的指令也就是生成的test.o文件
3生成符号表在链接的步骤产生作用 一般是汇总全局范围内可以看到的符号例如main函数等像局部变量这些不会汇总。 链接 【在链接过程会完成的事情】
test.o---test
1链接目标文件test.o文件和链接库生成可执行程序二进制的程序
2合并段表
把目标文件里面相同段落的数据进行合并
3符号表的合成和重定位在汇编阶段生成的符号表
把每个目标文件里面的符号汇总在一起并附带相关的地址 二、运行环境
运行环境也叫执行环境用来实际执行代码。
在运行环境会完成的步骤
1. 程序必须载入内存中。在有操作系统的环境中一般这个由操作系统完成。在独立的环境中程序的载入必须由手工安排也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈stack存储函数的局部变量和返回地址。程序同时也可以使用静态static内存存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数也有可能是意外终止。 三、预处理符号详解
前言预处理指令就是在翻译环境--编译--预编译预处理这一步完成的。 1.预定义符号
1认识
预定义符号是C语言内置的一些符号有以下符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C其值为1否则未定义符号的输入细节前后分别是两个下划线_ _连起来__
2怎么使用
看一段代码
#includestdio.h
int main()
{printf(%s\n, __FILE__);printf(%d\n, __LINE__);printf(%s\n, __DATE__);printf(%s\n, __TIME__);printf(%s\n, __FUNCTION__);//文件正在编译哪个函数return 0;
}
看一下运行结果 解析
3最后的符号
__STDC__ //如果编译器遵循ANSI C其值为1否则未定义 说明当前的编译器为遵循ANSI C 2.#define
前言该预处理指令的种类繁多
1#define定义标识符
【语法结构】
#define name stuff 标识符的内容是多组多样的也就是将被替换的内容。 【使用例子1】定义标识符常量
#includestdio.h
#define MAX 100
#define M 35
int main()
{printf(%d\n,MAX);printf(%d\n,M);return 0;
}MAX的值就是100。MAX通通会被替换成100 M会在预处理阶段被替换成35不会计算成8 【使用例子2】定义字符串
#includestdio.h
#define STR abcde
int main()
{printf(STR);return 0;
}【使用例子3】定义类型
#includestdio.h
#define TNT int
int main()
{TNT a 520;printf(%d\n,a);return 0;
} 【使用例子4】可以替换成很多其他例子
#define MAX 1000
#define reg register //为 register这个关键字创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长可以分成几行写除了最后一行外每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf(file:%s\tline:%d\t \date:%s\ttime:%s\n ,\__FILE__,__LINE__ , \__DATE__,__TIME__ ) 注意#define定义后面最后不要加分号 2#define定义宏
【定义】 #define 机制包括了一个规定允许把参数替换到文本中这种实现通常称为宏macro或定义宏define macro。 #define name( parament-list ) stuff
//其中的 parament-list 是一个由逗号隔开的符号表它们可能出现在stuff中。
图解 注意事项1参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在参数列表就会被解释为stuff的一部分。 【使用例子1】宏的使用
#includestdio.h
#define ADD(x,y) (xy)
int main()
{int a 3;int b 5;int c ADD(a,b);printf(%d\n,c);return 0;
} 替换过程 【使用注意事项】括号的添加
#includestdio.h
#define ADD(x,y) xy
int main()
{int a 3;int b 5;int c 5*ADD(a,b);printf(%d\n,c);return 0;
}
我们会以为结果是40其实不是 ADD会被替换成35然后5*35才是最终处理的结果。 所以宏体需要添加括号。 做法
#includestdio.h
#define ADD(x,y) ((x)(y))
int main()
{int a 3;int b 5;int c 5*ADD(a,b);printf(%d\n,c);return 0;
}
3#define替换规则 1. 在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号。如果是它们首先被替换。 2. 替换文本随后被插入到程序中原来文本的位置。对于宏参数名被他们的值所替换。 3. 最后再次对结果文件进行扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。 注意 1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏不能出现递归。 2. 当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。 4#和##
【#的作用】把一个宏参数变成对应的字符串。
我们先看一段代码
#includestdio.h
int main()
{int a 10;printf(the value of a is %d\n, a);int b 10;printf(the value of a is %d\n, b);float c 3.14;printf(the value of a is %f\n, c);return 0;
}每一个printf中都有重复的内容是否可以将其包装起来呢因为函数无法传类型所以我们可以用宏
#includestdio.h
#define PRINT(n,format) printf(the value of n is format\n, n)
int main()
{int a 10;PRINT(a, %d);int b 10;PRINT(b, %d);float c 3.14;PRINT(c, %lf);return 0;
} 宏替换后 【正确做法】需要用到#
#includestdio.h
#define PRINT(n,format) printf(the value of #n is format\n, n)
int main()
{int a 10;PRINT(a, %d);int b 10;PRINT(b, %d);float c 3.14;PRINT(c, %lf);return 0;
} 这样就可以把宏参数n替换成对应的字符串。比如a传给nn是一个宏参数替换成对应的宏参数就是a。 【##的作用】##可以把位于它两边的符号合成一个符号它允许宏定义从分离的文本片段创建标识符。
【例子】
#includestdio.h
#define CAT(n,v) n##v
int main()
{int value10 666;printf(%d\n,CAT(value,10));return 0;
}
输出结果666 ##会把左右两个符号连接成一个符号n##v就变成nv于是value10就变成了value10就是666. 注意这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
5带副作用的宏参数
什么是副作用例如或者--操作赋如a之后a的值发生了改变这就是副作用宏参数使用这种操作符也会有这种后果。
x1;//不带副作用
x;//带有副作用
【例子】
#includestdio.h
#define MAX(a, b) ( (a) (b) ? (a) : (b) )
int main()
{int x 5;int y 8;int z MAX(x, y);printf(x%d y%d z%d\n, x, y, z);//输出的结果是什么return 0;
} 这就是宏参数的副作用 6宏和函数对比
【宏的优点】 1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。 所以宏比函数在程序的规模和速度方面更胜一筹。 2. 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。 宏的参数可以是类型
#includestdio.h
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{int* p MALLOC(10,int);if (p NULL){return;}return 0;
} 【宏的缺点】 1. 每次使用宏的时候一份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。 2. 宏是没法调试的。 3. 宏由于类型无关也就不够严谨。 4. 宏可能会带来运算符优先级的问题导致程容易出现错。 【宏与函数的对比】
属性#define宏函数代码长度每次使用时宏代码都会被插入到程序中。除了非常 小的宏之外程序的长度会大幅度增长函数代码只出现于一个地方每次使用这个函数时都调用那个地方的同一份代码执行速度更快存在函数的调用和返回的额外开 销所以相对慢一些操作符优先级宏参数的求值是在所有周围表达式的上下文环境里除非加上括号否则邻近操作符的优先级可能会产生不可预料的后果所以建议宏在书写的时候多些括 号。函数参数只在函数调用的时候求值一次它的结果值传递给函数。表达式的求值结果更容易预 测。带有副作用的参数参数可能被替换到宏体中的多个位置所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次结果更容易控制。参数类型宏的参数与类型无关只要对参数的操作是合法的它就可以使用于任何参数类型函数的参数是与类型有关的如果参数的类型不同就需要不同的函数即使他们执行的任务是不同的。调试宏是不方便调试的函数是可以逐语句调试的递归宏是不能递归的函数是可以递归的
7命名约定 把宏名全部大写 函数名不要全部大写 3.#undef
1定义
这条指令用于移除一个宏定义。
2
#includestdio.h
#define M 100
int main()
{printf(%d\n,M);#undef M//移除#define M 520//重新赋值printf(%d\n,M);return 0;
}4..命令行定义
1定义
编译器提供了一种能力允许在命令行中定义符号。用于启动编译过程。 就如Java在控制台操作一样 2作用
当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候这个特性有点用处。假 定某个程序中声明了一个某个长度的数组如果机器内存有限我们需要一个很小的数组但是另外一个机器内存大写我们需要一个数组能够大写。 当前VS无法验证 5.条件编译
1定义
可以选择性选择某一条代码进行编译
如调试性的代码删除可惜保留又碍事所以我们可以选择性的编译。
2第一种条件编译指令
【形式】
#if 常量表达式
//...
#endif 【代码】
#includestdio.h
int main()
{
#if 1printf(我喜欢你\n);
#endif#if 0printf(我不喜欢你\n);
#endifreturn 0;
}
运行起来看结果 3第二种条件编译指令
【形式】这种用于多分支的条件编译如if…else if…else语句类似
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif 无论什么情况最多也只会选择一块代码进行编译。 【代码】
#includestdio.h
int main()
{
#if 1printf(我喜欢你\n);
#elif 1printf(我不喜欢你\n);
#elif 1100printf(爱你一生一世\n);
#elseprintf(不爱你\n);
#endifreturn 0;
}
运行结果
4判断是否被定义
【形式1】
#if defined(symbol)//语句
#endif
#includestdio.h
#define M 200
int main()
{
#if defined(M)printf(如果M被定义则会打印这句话\n);
#endif
#if defined(N)printf(如果N被定义则会打印这句话\n);
#endifreturn 0;
} 它只会判断这个符号是否存在不会判断其值的真假 【形式2】
#ifdef 符号//语句
#endif
#includestdio.h
#define M 200
int main()
{
#ifdef Mprintf(如果M被定义则会打印这句话\n);
#endif
#ifdef Nprintf(如果N被定义则会打印这句话\n);
#endifreturn 0;
} 【形式3】
#if !defined(symbol)//语句
#ifndef symbol
includestdio.h
#define M 200
int main()
{
#if !defined(M)printf(如果M没有被定义则会打印这句话\n);
#endif
#if !defined(N)printf(如果N没有被定义则会打印这句话\n);
#endifreturn 0;
} 5嵌套指令
【形式】
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif 6.头文件包含
1两种头文件包含的形式 1.包含本地的文件自己的.h文件 #includexxxxxx.h 2.包含标准库的头文件 #includexxxx.h 【本地文件包含】#includexxxxxx.h
查找策略先在源文件所在目录下查找如果该头文件未找到编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。
【库文件包含】#includexxxx.h
查找头文件直接去标准路径下去查找如果找不到就提示编译错误。 写的时候需要区分不同种类的头文件 2嵌套头文件
嵌套使用头文件会增加程序的负担所以每个头文件只需要被包含一次即可
【方法1】使用条件编译
每个头文件的开头写
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
【方法2】在头文件前加上这一句话
#pragma once