C语言中的程序环境与预处理实例分析


本篇内容主要讲解“C语言中的程序环境与预处理实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言中的程序环境与预处理实例分析”吧!在AN免费云主机域名SI C的任何一种实现中,存在两个不同的环境第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令,也就是从,c文件到.exe文件;第2种是执行环境,它用于实际执行代码;翻译环境是由编译器提供的,而执行环境是由操作系统提供的。如MSVC,DEV C++,Codeblocks这些编译软件都是集成开发环境,也就是集成了编辑,编译,链接和调试等功能。从源文件到可执行程序可以分为编译和链接两步,在编译阶段源文件变成了目标文件,在链接阶段目标文件变成了可执行程序。组成程序的每个源文件通过编译过程分别转化成目标文件;每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序;链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且链接器也可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。图解:编译本身可以分为预编译(预处理),编译和汇编。预编译:在预编译阶段会将#include引用的头文件给输入到文件里面,进行#define定义的标识符的替换,以及将注释给删除,因为注释是给程序员看的,不是给电脑看的;编译:在这个过程中会将C语言代码翻译成汇编代码,编译器会对代码进行词法分析,语法分析,语义分析,符号汇总;汇编:会把在编译阶段形成的汇编代码翻译成二进制的指令,并将汇总的符号形成一个符号表;在编译完成之后,就会开始链接,链接过程会合成段表,也就是将目标文件捆绑在一起,以及将符号表合并并进行重定位,最后生成可执行程序。程序执行的过程:1.程序必须载入内存中。在有操作系统的环境中,一般这个过程由操作系统完成,在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。2.程序开始执行,并调用main函数。3.开始执行程序代码,这个时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址,程序同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程一种保留它们的值。4.终止程序。正常终止main函数,也有可能是意外终止。预定义符号都是语言内置的__FILE__ //进行编译的源文件__LINE__ //当前代码的行号__DATE__ //文件被编译时的日期__TIME__ //文件被编译时的时间__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义预定义符号的使用:#define name stuff举例:在define定义标识符的时候,不要在最后加上;如下面这种情况,会出现语法错误#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或宏定义宏的声明方式如下:#define name(parament-list) stuff其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中把name(parament-list)这个整体称为宏注意:参数列表的左括号必须与name紧贴,如果两者之间存在空格,参数列表就会被解释为stuff的一部分,语法就是这么规定的。接下来是宏的使用:比如用宏实现一个数的平方:语句SQUARE(6)就会替换成6 * 6;解释:宏先是接受一个参数,SQRARE(n)中的n就变成了6,其后宏的内容也就由n * n变成了6 * 6,再将6 * 6替换到程序中使用宏的位置。但是,这个宏这么写存在一个问题,如下代码:看上去似乎最后的结果是16,然而实际上参数n会被替换成1 + 3,这样最终替换的内容是1 + 3 * 1 + 3,这条表达式最终的结果是7.所以需要在n的左右两边加上一对括号,如下:#define SQUARE(n) (n) * (n)再看另外一个宏定义:#define DOUBLE(n) (n) + (n)代码:看上去最终结果似乎是30,然而替换后语句实际上是所以最终结果是18所以为了保证获得想要的结果,宏定义表达式两边还需要加上一对括号#define DOUBLE ((n) + (n))所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中
的操作符或邻近操作符之间产生不可预料的相互作用。在程序中扩展#define定义符号和宏时,需要涉及几个步骤1.在调用宏时,首先对宏括号中的参数进行检查,看看是否包含由#define定义的符号,如果有,这些符号首先被替换。2.替换文本后,文本被插入到程序中原来文本的位置,对于宏,参数名被对应的值所替换。3.最后,再次对结果文件进行扫描,查看替换过后的内容是否还有#define定义的符号,如果有,则重复上述处理过程注意:1.宏参数和#define定义中可以出现其他#define定义的符号,但是对于宏,不能实现递归。2.当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索。比如:语句printf(“%s”, “a”);中的a并不会被替换成123如何把参数插入到字符串中?如下代码:输出的结果是abcdef发现字符串是有自动相连的特点的看下面这个代码:最终输出的结果是:the value of a is 6所以#VALUE会被预处理器在预处理阶段预处理为”VALUE”接下来看看##的作用:##可以把位于它两边的符号合成一个符号,并且允许宏定义从分离的文本片段创建标识符。如下代码:注意:连接之后产生的符号必须是已经定义的,否则结果就是非法的。当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能会出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性后果。比如:x + 1;//不带有副作用x++; //带有副作用如下代码可以证明副作用的宏参数带来的问题在代码预处理之后z = ( (x++) > (y++) ? (x++) : (y++));所以最终结果是x=6 y=10 z=9宏通常被用于执行简单的运算,比如在两个数中找出较大的一个#define MAX(a, b) ((a)>(b)?(a):(b))对于为什么不用函数来完成这个任务,有两个原因:1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间多,所以此时宏在程序的规模和运算方面更胜一筹。2.函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用,而宏可以适用于整型,浮点型,长整型,宏是类型无关的当然宏也是有缺点的:1.每次使用宏的时候,会将宏定义的代码插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度;2.宏是没法进行调试的,因为在预处理阶段,宏定义的符号已经发生了替换,此时调试看到的代码和实际上运行时的代码是有所差异的;3.宏由于类型无关,也就不够严谨了;4.宏可能会带来运算级优先的问题,导致程序容易出错;宏有时候也可以做到函数做不到的事情,比如宏的参数可以出现类型,但是函数不行如下代码:预处理替换后:会节省部分代码。总的来对比一下宏和函数的区别:函数和宏的使用语法很相似,所以语言本身没法帮助区分二者,所以平时的命名习惯是:宏名全部大写函数名不要全部大写这条指令用于移除一个宏定义语法:#undef name使用:许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)在编译一个程序的时候如果要将一条语句或者一组语句编译或者放弃掉是很方便的,因为有一个叫条件编译的东西。对于调试性的代码,删除比较可惜,保留又会碍事,所以可以选择性的去编译。如下代码:1.#if 常量表达式
//…
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif2.多个分支的条件编译
#if 常量表达式
//…
#elif 常量表达式
//…
#else
//…
#endif3.判断是否被定义
#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbo4.嵌套指令
#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#include指令可以使另一个文件被编译,会让被包含的头文件出现在#include指令的地方这种替换的方式很简单,预处理器会先删除这条指令,并用包含文件里的内容进行替换,如果这个文 件被包含了10次,那实际上就会被编译10次本地文件包含:#include “fliename.h”查找方法:先在源文件的目录下去查找,如果该头文件未被找到,编译器就会像去查找库函数的头文件一样在标准位置去查找头文件,如果还找不到就提示编译错误。库文件包含:#include 查找方法:直接在标准路径下去查找,如果找不到就提示编译错误。虽然可以对库文件也采用””的包含方式,但是当目录下的文件非常多的时候,这样查找起来的效率就会低一些了,而且也不容易去区分是库文件还是头文件了。如图:common.h和common.c是公共模块test1.h和test1.c使用了公共模块test2.h和test2.c使用了公共模块test.h和test.c使用了test1模块和test2模块这样最终的程序中就会包含两次common.h了,等于有2份common.h的内容,会造成代码的重复。对此可以采用条件编译的方式来解决这个问题在引用每个头文件时在开头写上这么一个内容:如果没有定义标识符__STDIO_H__就定义__STDIO_H__并且去包含头文件如果下次还遇到包含头文件的代码,由于__STDUI_H__已经被定义过,所以也就不会进行第二次包含了或者对于在头文件的开头也可以这么写:也可以避免头文件的重复引入4.其他预处理指令比如:#error#pragma#pragma#line到此,相信大家对“C语言中的程序环境与预处理实例分析”有了更深的了解,不妨来实际操作一番吧!这里是百云主机网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

相关推荐: PHP8.2有哪些改进

这篇文章主要介绍了PHP8.2有哪些改进的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇PHP8.2有哪些改进文章都会有所收获,下面我们一起来看看吧。PHP8.2是PHP语言现代化进程中的一个重要的里程碑。除了令人兴奋地新特性和改进…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 4天前
下一篇 4天前

相关推荐