iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C语言中的程序环境与预处理实例分析
  • 550
分享到

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

2023-07-02 16:07:29 550人浏览 泡泡鱼
摘要

本篇内容主要讲解“C语言中的程序环境与预处理实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言中的程序环境与预处理实例分析”吧!1.程序的翻译环境和执行环境在ANSI C的任何一种实现

本篇内容主要讲解“C语言中的程序环境与预处理实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言中的程序环境与预处理实例分析”吧!

1.程序的翻译环境和执行环境

在ANSI C的任何一种实现中,存在两个不同的环境

  • 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令,也就是从,c文件到.exe文件;

  • 第2种是执行环境,它用于实际执行代码;

翻译环境是由编译器提供的,而执行环境是由操作系统提供的。

如MSVC,DEV c++,Codeblocks这些编译软件都是集成开发环境,也就是集成了编辑,编译,链接和调试等功能。

2.详解编译和链接

2.1程序翻译环境下的编译和链接

从源文件到可执行程序可以分为编译和链接两步,在编译阶段源文件变成了目标文件,在链接阶段目标文件变成了可执行程序。

组成程序的每个源文件通过编译过程分别转化成目标文件;

每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序;

链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且链接器也可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

图解:

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

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

2.2深入编译和链接过程

编译本身可以分为预编译(预处理),编译和汇编。

预编译:在预编译阶段会将#include引用的头文件给输入到文件里面,进行#define定义的标识符的替换,以及将注释给删除,因为注释是给程序员看的,不是给电脑看的;

编译:在这个过程中会将C语言代码翻译成汇编代码,编译器会对代码进行词法分析,语法分析,语义分析,符号汇总;

汇编:会把在编译阶段形成的汇编代码翻译成二进制的指令,并将汇总的符号形成一个符号表;

在编译完成之后,就会开始链接,链接过程会合成段表,也就是将目标文件捆绑在一起,以及将符号表合并并进行重定位,最后生成可执行程序。

2.3运行环境

程序执行的过程:

  • 程序必须载入内存中。在有操作系统的环境中,一般这个过程由操作系统完成,在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

  • 程序开始执行,并调用main函数。

  • 开始执行程序代码,这个时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址,程序同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程一种保留它们的值。

  • 终止程序。正常终止main函数,也有可能是意外终止。

3.预处理详解

3.1预定义符号

预定义符号都是语言内置的

__FILE__       //进行编译的源文件

__LINE__      //当前代码的行号

__DATE__    //文件被编译时的日期

__TIME__     //文件被编译时的时间

__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

预定义符号的使用:

int main(){printf("file:%s\nline:%d\ndata:%s\ntime:%s\n", __FILE__, __LINE__, __DATE__, __TIME__);return 0;}

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

3.2#define

3.2.1#define定义的标识符

#define name stuff

举例:

#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定义标识符的时候,不要在最后加上;

如下面这种情况,会出现语法错误

#define NUM 100;int main(){int a = 0;if (1)a = NUM;elsea = 0;return 0;}
3.2.2#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或宏定义

宏的声明方式如下:

#define name(parament-list) stuff

其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中

把name(parament-list)这个整体称为宏

注意:

参数列表的左括号必须与name紧贴,如果两者之间存在空格,参数列表就会被解释为stuff的一部分,语法就是这么规定的。

接下来是宏的使用:

比如用宏实现一个数的平方:

#define SQUARE(n) n * nint main(){SQUARE(6);return 0;}

语句SQUARE(6)就会替换成6 * 6;

解释:宏先是接受一个参数,SQRARE(n)中的n就变成了6,其后宏的内容也就由n * n变成了6 * 6,再将6 * 6替换到程序中使用宏的位置。

但是,这个宏这么写存在一个问题,如下代码:

#define SQUARE(n) n * nint main(){printf("%d\n", SQUARE(1 + 3));return 0;}

看上去似乎最后的结果是16,然而实际上参数n会被替换成1 + 3,这样最终替换的内容是1 + 3 * 1 + 3,这条表达式最终的结果是7.

所以需要在n的左右两边加上一对括号,如下:

#define SQUARE(n) (n) * (n)

再看另外一个宏定义:

#define DOUBLE(n) (n) + (n)

代码:

#define DOUBLE(n) (n) + (n)int main(){printf("%d\n", 5 * DOUBLE(3));return 0;}

 看上去最终结果似乎是30,然而替换后语句实际上是

printf("%d\n", 5 * (3) + (3));

所以最终结果是18

所以为了保证获得想要的结果,宏定义表达式两边还需要加上一对括号

#define DOUBLE ((n) + (n))

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中
的操作符或邻近操作符之间产生不可预料的相互作用。 

3.2.3#define替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤

  • 在调用宏时,首先对宏括号中的参数进行检查,看看是否包含由#define定义的符号,如果有,这些符号首先被替换。

  • 替换文本后,文本被插入到程序中原来文本的位置,对于宏,参数名被对应的值所替换。

  • 最后,再次对结果文件进行扫描,查看替换过后的内容是否还有#define定义的符号,如果有,则重复上述处理过程

注意:

  • 宏参数和#define定义中可以出现其他#define定义的符号,但是对于宏,不能实现递归

  • 当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索。

比如:

#define a 123int main(){printf("%s", "a");return 0;}

语句printf("%s", "a");中的a并不会被替换成123

3.3.4#和##

如何把参数插入到字符串中?

如下代码:

int main(){printf("abc""def");return 0;}

输出的结果是abcdef

发现字符串是有自动相连的特点的

看下面这个代码:

#define PRINT(FORMAT, VALUE) printf("the value of "#VALUE " is "FORMAT"\n", VALUE)int main(){int a = 6;PRINT("%d", a);return 0;}

最终输出的结果是:

the value of a is 6

所以#VALUE会被预处理器在预处理阶段预处理为"VALUE"

接下来看看##的作用:

##可以把位于它两边的符号合成一个符号,并且允许宏定义从分离的文本片段创建标识符。

如下代码:

#define MAXMIN 6#define MIDDLE MAX##MINint main(){printf("%d\n", MIDDLE);return 0;}

注意:连接之后产生的符号必须是已经定义的,否则结果就是非法的。

3.2.5带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能会出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性后果。

比如:

x + 1;//不带有副作用

x++;  //带有副作用

如下代码可以证明副作用的宏参数带来的问题

#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;}

 在代码预处理之后

z = ( (x++) > (y++) ? (x++) : (y++));

所以最终结果是x=6 y=10 z=9

3.2.6宏和函数对比

宏通常被用于执行简单的运算,比如在两个数中找出较大的一个

#define MAX(a, b) ((a)>(b)?(a):(b))

对于为什么不用函数来完成这个任务,有两个原因:

  • 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间多,所以此时宏在程序的规模和运算方面更胜一筹。

  • 函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用,而宏可以适用于整型,浮点型,长整型,宏是类型无关的

当然宏也是有缺点的:

  • 每次使用宏的时候,会将宏定义的代码插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度;

  • 宏是没法进行调试的,因为在预处理阶段,宏定义的符号已经发生了替换,此时调试看到的代码和实际上运行时的代码是有所差异的;

  • 宏由于类型无关,也就不够严谨了;

  • 宏可能会带来运算级优先的问题,导致程序容易出错; 

宏有时候也可以做到函数做不到的事情,比如宏的参数可以出现类型,但是函数不行

如下代码:

#define MALLOC(num, type)\(type *)malloc(num * sizeof(type))...//使用MALLOC(10, int);//类型作为参数

 预处理替换后:

(int *)malloc(10 * sizeof(int));

会节省部分代码。

总的来对比一下宏和函数的区别:

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

函数和宏的使用语法很相似,所以语言本身没法帮助区分二者,所以平时的命名习惯是:

宏名全部大写

函数名不要全部大写

3.3#undef 

这条指令用于移除一个宏定义

语法:

#undef name

使用: 

#define MAX 5int main(){printf("%d\n", MAX);#undef MAXreturn 0;}

3.4命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)

#include <stdio.h>int main(){  int array [ARRAY_SIZE];  int i = 0;  for(i = 0; i< ARRAY_SIZE; i ++) {    array[i] = i; }  for(i = 0; i< ARRAY_SIZE; i ++) {    printf("%d " ,array[i]); }  printf("\n" );  return 0;}

3.5条件编译

在编译一个程序的时候如果要将一条语句或者一组语句编译或者放弃掉是很方便的,因为有一个叫条件编译的东西。

对于调试性的代码,删除比较可惜,保留又会碍事,所以可以选择性的去编译。

如下代码:

#define __DEBUG__int main(){int arr[10] = { 0 };int i = 0;for (i = 0; i < 10; i++){arr[i] = i;#ifdef __DEBUG__printf("%d ", arr[i]);//为了观察数组是否被赋值成功#endif}return 0;}

#if 常量表达式
        //...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
        //..
#endif

多个分支的条件编译
#if 常量表达式
        //...
#elif 常量表达式
        //...
#else
        //...
#endif

判断是否被定义
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbo

嵌套指令
#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

3.6文件包含

#include指令可以使另一个文件被编译,会让被包含的头文件出现在#include指令的地方

这种替换的方式很简单,预处理器会先删除这条指令,并用包含文件里的内容进行替换,如果这个文  件被包含了10次,那实际上就会被编译10次

3.6.1头文件被包含的方式

本地文件包含:

#include "fliename.h"

查找方法:先在源文件的目录下去查找,如果该头文件未被找到,编译器就会像去查找库函数的头文件一样在标准位置去查找头文件,如果还找不到就提示编译错误。

库文件包含:

#include <filename.h>

查找方法:直接在标准路径下去查找,如果找不到就提示编译错误。

虽然可以对库文件也采用""的包含方式,但是当目录下的文件非常多的时候,这样查找起来的效率就会低一些了,而且也不容易去区分是库文件还是头文件了。

3.6.2嵌套文件包含

如图:

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

  • common.h和common.c是公共模块

  • test1.h和test1.c使用了公共模块

  • test2.h和test2.c使用了公共模块

  • test.h和test.c使用了test1模块和test2模块

这样最终的程序中就会包含两次common.h了,等于有2份common.h的内容,会造成代码的重复。

对此可以采用条件编译的方式来解决这个问题

在引用每个头文件时在开头写上这么一个内容:

#ifndef __STDIO_H__#define __STDIO_H__#include <stdio.h>#endif

如果没有定义标识符__STDIO_H__就定义__STDIO_H__并且去包含头文件

如果下次还遇到包含头文件的代码,由于__STDUI_H__已经被定义过,所以也就不会进行第二次包含了

或者对于在头文件的开头也可以这么写:

#pragma once#include <stdio.h>

也可以避免头文件的重复引入

其他预处理指令

比如:

#error

#pragma

#pragma

#line

到此,相信大家对“C语言中的程序环境与预处理实例分析”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

--结束END--

本文标题: C语言中的程序环境与预处理实例分析

本文链接: https://www.lsjlt.com/news/342983.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C语言中的程序环境与预处理实例分析
    本篇内容主要讲解“C语言中的程序环境与预处理实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言中的程序环境与预处理实例分析”吧!1.程序的翻译环境和执行环境在ANSI C的任何一种实现...
    99+
    2023-07-02
  • C语言程序环境中的预处理实例分析
    本篇内容介绍了“C语言程序环境中的预处理实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、翻译环境整个翻译环境大致就可以画成这样一张...
    99+
    2023-06-29
  • C语言中程序环境与预处理的示例分析
    这篇文章主要介绍了C语言中程序环境与预处理的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。c语言代码的实现包含两种环境1.翻译环境,将源代码转化成可执行的机器指令2....
    99+
    2023-06-29
  • C语言程序环境和预处理详解分析
    目录一、程序的翻译环境和运行环境程序的翻译环境链接阶段执行环境(运行环境)二、预处理详解预定义符号#define定义标识符#define定义宏#define 替换规则#和##两个预处...
    99+
    2024-04-02
  • C语言中的程序环境与预处理详情
    目录1.程序的翻译环境和执行环境2.详解编译和链接2.1程序翻译环境下的编译和链接2.2深入编译和链接过程2.3运行环境3.预处理详解3.1预定义符号3.2#define3.2.1#...
    99+
    2024-04-02
  • C语言程序的编译与预处理实例分析
    本篇内容介绍了“C语言程序的编译与预处理实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!程序的翻译环境和执行环境在ANSIC的任何一种...
    99+
    2023-06-30
  • c语言的程序环境与预处理详解
    目录1.翻译环境2.运行环境3.预处理详解3.1#define定义的符号3.2#define定义的宏3.3#define的替换规则3.4#与##4.宏与函数对比5.#undef6.条...
    99+
    2024-04-02
  • C语言程序环境中的预处理详解
    目录一、翻译环境二、执行环境三、预处理1. 预处理符号2. #define定义标识符3. #define定义宏4. #和##5. 宏和函数的对比6. 条件编译7. 文件包含总结一、翻...
    99+
    2024-04-02
  • 一起来学习C语言的程序环境与预处理
    目录1.程序的翻译环境和执行环境2.gcc C语言编译器来演示编译过程2.1编译2.2编译:2.3运行环境3详解预处理3.1预定义符号3.2#define3.2.1#define定义...
    99+
    2024-04-02
  • C语言的程序环境与预处理你真的了解吗
    目录1.翻译环境2.运行环境3.预处理详解3.1#define定义的符号3.2#define定义的宏3.3#define的替换规则3.4#与##4.宏与函数对比5.#undef6.条...
    99+
    2024-04-02
  • C语言中预处理的示例分析
    小编给大家分享一下C语言中预处理的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!#define定义宏带副作用的宏参数我们来看如下一段代码结果分别为12,1...
    99+
    2023-06-25
  • C语言中程序环境和预处理的详细图文讲解
    目录1. 程序的翻译环境和执行环境 2. 详解编译+链接 2.1 翻译环境2.2 编译本身也分为几个阶段2.3 运行环境  3. 预处理详解3.1 预定...
    99+
    2023-02-16
    c语言程序环境和预处理的区别 c语言的编译预处理 c语言程序环境
  • CSS中预处理语言的示例分析
    小编给大家分享一下CSS中预处理语言的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Less 是一门 CSS 预处理语言...
    99+
    2024-04-02
  • C语言循环与分支语句实例分析
    本篇内容主要讲解“C语言循环与分支语句实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言循环与分支语句实例分析”吧!写在开始0表示假,非0表示真。1. 分支语句分支语句也叫做条件选择语...
    99+
    2023-06-30
  • C语言之预处理命令的示例分析
    这篇文章主要介绍C语言之预处理命令的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!c提供的预处理功能有:宏定义文件包含条件编译为了与其她c语句区分,命令经常以符号“#”开头。宏定义#define 标识符 字符...
    99+
    2023-06-14
  • C语言程序的编译与预处理详解
    目录一、程序的编译1、 编译阶段2、链接二、预处理详解1、预定义符号2、#define定义的标识符3、#define定义的宏4、#unef总结一、程序的编译 我们写的源文件(*.c)...
    99+
    2024-04-02
  • C语言排序的原理实例分析
    这篇文章主要介绍“C语言排序的原理实例分析”,在日常操作中,相信很多人在C语言排序的原理实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C语言排序的原理实例分析”的疑惑有所帮助!接下来,请跟着小编一起来...
    99+
    2023-06-17
  • C语言分支与循环实例代码分析
    这篇文章主要介绍“C语言分支与循环实例代码分析”,在日常操作中,相信很多人在C语言分支与循环实例代码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C语言分支与循环实例代码...
    99+
    2024-04-02
  • C语言中循环的示例分析
    这篇文章主要为大家展示了“C语言中循环的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C语言中循环的示例分析”这篇文章吧。(壹)while语句1.1while的执行流程比如我们实现:在屏...
    99+
    2023-06-29
  • C语言程序环境编译+链接理论
    目录一、程序的翻译环境(编译和链接)二、程序的运行环境一、程序的翻译环境(编译和链接) 在ANSI C 的任何一种实现中,存在两个不同的环境: 第一种是翻译环境,在这个环境中源代码被...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作