C语言-程序环境与预处理

news2024/11/25 4:51:44

程序环境与预处理

  • 程序环境
    • 翻译环境(编译+链接)
      • 预编译
      • 编译
      • 汇编
      • 链接
    • 执行环境
  • 预处理
      • 预定义符
      • #define定义的标识符
      • #define定义宏
      • #define替换规则
      • 宏的命名约定
      • 带副作用的宏参数
      • 宏和函数的比较
    • 其它
      • #和##的使用
        • 字符串常量化运算符#
        • 标记粘贴运算符##
      • 命令行定义
      • 文件包含
      • 常见的编译指令
        • #undef
        • 条件编译指令
      • 嵌套文件包含

程序环境

在ANSI C的任何一种实现过程中,存在两个顺序依次的环境。

第一种环境是翻译环境,在这个环境中源代码被转换为可执行的机器指令(二进制指令)。
第二种是执行环境,它用于执行实际代码。

在这里插入图片描述
下面我们详细的了解下翻译环境

翻译环境(编译+链接)

翻译环境包括编译和链接两个部分
在VS中,编译器叫cl.exe,链接器叫link.exe

如下:
在这里插入图片描述
一个工程中可能包含多个.c和.h文件。
如下:
在这里插入图片描述
而编译过程又包括预编译,编译和汇编。翻译过程可以细化如下:
在这里插入图片描述

预编译

我们通过gcc编译器来生成.i的文件,命令如下:

gcc test.c -E -o test.i

生成的.i文件和源代码 的比较如下:

在这里插入图片描述

1 展开头文件。预编译的时候会展开源文件中包含的所有头文件。如①
2 #define所定义的宏命令的符号会被替换掉,同时删除#define所定义的宏命令。如②
3 删除注释。如③

编译

gcc test.c -S

生成的文件如下:
在这里插入图片描述
可以观察到此时的格式为汇编代码。

1 词法,语法,语义分析,主要是编译器根据语法标准进行判断。
2 在语法分析的时候可以得到很多的符号,在整个编译和链接的过程中,我们将函数名和变量名作为它们对应的符号名。符号的汇总是有规则的,我们只汇总函数名,全局变量和静态数据。
在上述的代码中,被汇总的符号只有main。
而在下图的代码中,被汇总的符号有:b,main,printf

在这里插入图片描述

汇编

 gcc test.c -c

在这里插入图片描述

1 汇编就是利用汇编器将汇编代码转换为机器可以执行的指令。 可以看到经过汇编后,该文件转换为二进制文件。
2 每一个目标文件都会有一个符号表,该符号表是对在编译时的符号分析的汇总,在符号表内,每一个符号都对应有一个符号值。对于函数和变量来说,这个符号值就是它们的地址。

如下:
在这里插入图片描述
在test.c文件中,Add函数只是声明,并没有定义。因此,它的地址是一个随机值

链接

链接器可以将上述生成的目标文件链接起来形成一个可执行文件。
1 符号表的合并和重定向
将所有目标文件中的符号表,进行合并,舍弃无效的地址,合并为一个新的符号表。

以上面的为例。
在这里插入图片描述
2 合并段表。上述生成的每一个目标文件中都含有各种类型的段,
大致如下
在这里插入图片描述
合并段表就是将每一个目标文件中的相同的段进行合并。
3 与链接库的合并。会引入标准库中的函数等,如上述的printf函数。

执行环境

在程序的执行过程中:

(1)程序必须载入内存中。在有操作系统的环境中,一般这个由操作系统完成。在独立的环境中,程序的载入必须手动安排,也可以通过可执行代码置于只读内存中完成。
(2)程序的执行开始,接着调用main函数。
(3)开始执行程序代码,程序将使用一个运行时堆栈(stack),存放函数的局部变量和返回地址;程序同时也可以使用静态(static)内存,存储在静态内存中的变量在程序的整个执行过程中一直会保留它的值。
(4)终止程序。正常main函数执行结束或者因意外终止。

预处理

预定义符

__FILE__  进行编译的源文件
__LINE__  文件当前的行号
__DATE__  文件被编译的日期
__TIME__  文件被编译的时间
__STDC__  如果编译器遵循ANSI C标准,其值为1,否则未定义
#include <stdio.h>

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

在这里插入图片描述
我们通过预编译可以看到,如下:
在这里插入图片描述

#define定义的标识符

#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
#define DEBUG_PRINT printf("%s %s ",\  //语句过长,可以用\分行
__FILE__\
__DATE__)

这里我们举两个例子。

#include <stdio.h>

#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
#define DEBUG_PRINT printf("%s %s ",  \
__FILE__,\
__DATE__)  //语句过长,可以用\分行

// int main()
// {
//     printf("%s %d %s %s\n",__FILE__,__LINE__,__DATE__ ,__TIME__);
//     return 0;
// }

int main()
{
    do_forever;
    return 0;
}

在这里插入图片描述
我们通过预编译可以看到,如下:
在这里插入图片描述

int main()
{
   switch(2)
   {
    case 1:
    break;
    case 2:
    break;
    case 3:
    break;
    default :
    
    break;
   }

      switch(2)
   {
    case 1:
    CASE 2:
    CASE 3:
    break;
    default :
    break;
   }
    return 0;
}

在这里插入图片描述
值得我们注意的是,在定义时,要避免使用 ;,否则容易造成语法错误.
在这里插入图片描述

#define定义宏

宏的声明方式

#define name(parament-list) stuff

其中的parament-list是一个由,隔开的符号表,他们可能出现在stuff中。
注意参数列表的左括号必须与name相邻,如果两者之间存在任何空白,参数列表会被解释为stuff的一部分。

我们通过3个例子说明。

#define SQUARE(X) X*X

int main()
{
    printf("%d\n",SQUARE(5));
    return 0;
}

在这里插入图片描述
我们通过预编译可以看到,如下:
在这里插入图片描述

#define SQUARE(X) X*X

int main()
{
    printf("%d\n",SQUARE(5+1));
    return 0;
}

在这里插入图片描述
我们看到其结果并不是我们认为的36,可以通过预编译结果看到问题
在这里插入图片描述
我们可以对宏定义中的stuff添加括号。
如下:

#define SQUARE(X) (X)*(X)

int main()
{
    printf("%d\n",SQUARE(5+1));
    return 0;
}

在这里插入图片描述

#define DOUBLE(X) (X)+(X)

int main()
{

    printf("%d\n",3*DOUBLE(5)); 
    return 0;
}

在这里插入图片描述
这里结果是20的原因,也是一样的,需要添加括号。
在这里插入图片描述

#define DOUBLE(X) ((X)+(X))

int main()
{

    printf("%d\n",3*DOUBLE(5)); 
    return 0;
}

在这里插入图片描述

因此,在定义宏的时候,应尽量可能多的增加括号,避免操作符的优先级或者邻近操作符之间的相互影响。

#define替换规则

(1)在预编译时,首先会对代码进行检查,看看是否包含任何由#define定义的符号,如果有它们首先被替换
(2)替换文本后插入到程序中原来文本所在的位置
(3)再次对文本进行检查,是否包含#define定义的符号。如果有,则重复上述操作。(这是因为可能存在一个宏调用另一个宏的情况)
注意:
(1)宏不能出现递归的情况,但可以出现在其它宏的定义中
(2)当预处理器搜索#define定义的符号的时候,在字符串常量中出现的所定义的符号,将不会被搜索替换。

宏的命名约定

宏名要全部大写;函数名不要全部大写

带副作用的宏参数

什么叫带有副作用,
如下:

	int a = 10;
	int b = a + 1;//没有副作用,a值没有改变
	int c = a++; //有副作用,a值发生改变

下面通过一个例子来说明

#define MAX(X,Y) ((X)>(Y)?(X):(Y))

int main()
{
    int a=3;
    int b=5;
    int c=MAX(a++,b++);
    printf("%d %d %d",a,b,c);
    return 0;
}

在这里插入图片描述
结果为4,7,6
通过编译预处理的结果,如下图,我们分析:
在这里插入图片描述
后置++,是先运算,再++;首先3>5,不成立,他将会跳到:后的表达式。之后a++,b++,a=4,b=6…在跳到冒号之后,会将b的值作为条件表达式的结果赋给c,那么c=6,此后b再++,b=7

与之相类似的函数,对比如下:

int Max(int x,int y)
{
    return (x>y?x:y);
}
int main()
{
    int a=3;
    int b=5;
    int c=Max(a++,b++);//a=4 b=6  c=5
    printf("%d %d %d",a,b,c);
    return 0;
}

同上,a=3,b=5,首先会被带入函数,之后a++,b++,则a=4,b=6.在函数的内部3>5,不成立,则函数会返回5,那么c=5

宏和函数的比较

宏的优点:
(1) 在较小型的计算中,多使用宏
(2) 用于调用函数和从函数返回的代码可能比实际执行这个小型的运算所需要的时间更多
(3)宏不需要特定的类型。函数只能在类型合适的表达式上使用,宏可以使用于整型,长整型等。 宏的缺点:
(1)每次使用宏时,一份宏定义的代码将会插入程序中,除非宏比较短,否则会大幅度增加程序的长度。
(2)宏是不能调试的
(3)宏由于类型无关,也就不够严谨
(4)宏可能带来运算优先级的问题,导致程序容易出错

因此,当功能简单时,可以考虑使用宏来实现;当功能比较复杂时,可以使用函数来实现。
在这里插入图片描述

其它

#和##的使用

字符串常量化运算符#

我们观察下例

int main()
{
	int a = 3;
	printf("the num of" " value is %d\n",a);
	return 0;
}

在这里插入图片描述
通过结果我们可以判断出两个相邻的字符串具有自动连接为一个字符串的特点。
因此,我们可以用#define进行以下操作

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

在这里插入图片描述

可以看出当宏参数为字符串时,我们可以将其置于其它字符串中。
当宏的参数不是字符串的时候,我们可以使用#,将宏参数改为对应的字符串,

如下:

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

在这里插入图片描述

标记粘贴运算符##

宏定义内的标记粘贴运算符会合并两个参数。

#define tokenpaster(n) printf("token" #n " =%d",token##n)

int main()
{
	int token34 = 40;
	tokenpaster(34);
	return 0;
}

在这里插入图片描述
我们可以通过预编译过程来看到宏中的替换,如下:
在这里插入图片描述

命令行定义

c编译器提供了一种能力,允许在命令行定义符号,用于启动编译过程。
如下例:

#include <stdio.h>

int main()
{
    int a=SIZE;
    printf("%d\n",a);
    return 0;
}

当我们这样表示 a=SIZE时,可以看到编译器是给出错误提示的。
在这里插入图片描述
但是,我们仍然可以在命令行输入指令,使其输出正确的结果。
在这里插入图片描述
参考链接:https://blog.csdn.net/weixin_38184741/article/details/89818658

文件包含

#include 指令可以使得其包含的文件被编译
头文件的包含包括两种方式:本地文件包含和库文件包含。
两种包含方式的查找策略是不同的。

本地文件包含:#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到;那么就去标准库函数路径下去查找,如果找不到就提示错误。
库文件包含:#include <filename>
查找策略:直接去标准库函数路径下去查找,如果找不到就提示错误。

值得注意的是,对于库函数也可以使用#include "filename"的方式,但是这样查找的方式效率低,而且不易区分是本地文件还是库文件。

常见的编译指令

#undef

#undef 移除一个宏定义

#undef NAME

条件编译指令

在编译程序时,如果条件成立,则可以进行编译
1

#if 常量表达式
//这里是内容
#endif
#define EXIST 1
#if EXIST
#define MAX 12
#endif

int main()
{
    int a=0;
    a=MAX;
    printf("%d\n",a);
    return 0;
}

在这里插入图片描述
可以看到MAX符号有被编译。
当我们改变条件,如下

#define EXIST 0
#if EXIST
#define MAX 12
#endif

int main()
{
    int a=0;
    a=MAX;
    printf("%d\n",a);
    return 0;
}

在这里插入图片描述
MAX 符号没有被编译。
2多条分支的条件编译

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

3 判断是否被定义(4种表达方式)

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol 

嵌套文件包含

#include 指令可以使得其包含的文件被编译,当同一个头文件被多次包含的时候,他也会多次被编译,会造成文件内容的重复,我们可以使用条件编译解决该问题。

#ifnedf __TEST_H__
#define __TEST_H__
//需要包含的头文件
#endif 

当首次执行该文件时,是没有定义 __TEST_H__的,那么他就会定义 __TEST_H__,同时执行需要包含的头文件;当再次执行该文件时, __TEST_H__已经被定义,就不会执行后面的预处理命令,这样,头文件就会只被包含一次。
也可以直接使用

#pragma once

参考链接:https://blog.csdn.net/cainiaochufa2021/article/details/125661575

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/534775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

fastled教程

文章目录 EVERY_N_MILLISECONDS(10)EVERY_N_SECONDS(5)fill_solid(leds, NUM_LEDS, CRGB::Red);fill_gradient_RGBfill_rainbow(leds, NUM_LEDS, i, 255 / NUM_LEDS);效果1fadeToBlackBy(leds, NUM_LEDS, 1); 效果2FastLED.setBrightness(2*i);// 效果3leds[i] CHSV(hue (i *…

scanf和scanf_s的区别、解决VS返回值被忽略的报错问题

一、scanf和scanf_s是什么&#xff1f; scanf()不会检查输入边界&#xff0c;可能造成数据溢出。 scanf_s()会进行边界检查。 二、分别分析 1.scanf scanf表示从键盘输入指定格式的数据。如&#xff1a;scanf("%d",x);指从键盘给x输入一个int型&#xff08;整型&…

【计网】【TCP】浅析TCP三次握手

前言 之前学习计网时不认真&#xff0c;TCP三次握手稀里糊涂就过去了&#xff0c;最近在重新查漏补缺计网这方面的知识&#xff0c;饭要一口一口吃&#xff0c;我就没有把其中涉及到的大量知识点写在此博客中&#xff0c;此文仅管中窥豹&#xff0c;之后再详细写吧。 笔记中有…

初步认识性能测试和完成一次完整的性能测试

上一篇博文主要通过两个例子让测试新手了解一下测试思想&#xff0c;和在做测试之前应该了解人几点&#xff0c;那么我们在如何完成一次完整的性能测试呢&#xff1f; 测试报告是一次完整性能测试的体现&#xff0c;所以&#xff0c;这里我给出一个完整的性能测试报告&#xff…

搞懂@DateTimeFormat 注解 和 对应的时间类型

通常而言&#xff0c;前端时间控件&#xff0c;一般情况下直接会传一个yyyy-MM-dd的日期字符串到后台。如果我们直接用java.util.Date类型来接收&#xff0c;是无法获取的。这是因为Date类型默认的格式为&#xff1a;Tue May 16 00:00:00 CST 2023这种。 举例 ApiOperation(val…

Games104现代游戏引擎学习笔记08

渲染那部分看的云里雾里的&#xff0c;等学完其他图形学的内容再回头开吧 游戏动画的三个挑战&#xff1a; 1.根据交互实时的反应各种变化 2.一帧时间里的庞大计算 3.更真实自然的表现 2D动画 sprite animation 把每一帧精灵循环绘制出来 2D技术实现3D效果 在各个视角采了一…

shell脚本——流编辑器“三剑客”之awk命令

shell脚本——流编辑器“三剑客”之awk命令 一、awk1、工作原理2、命令格式3、awk常见的内建变量&#xff08;可直接用&#xff09;4、按行输出文本5、按字段输出文本’:’6、通过管道、双引号调用shell命令 一、awk 1、工作原理 逐行读取文本&#xff0c;默认以空格或TAB键为…

射频放大器的原理和作用(射频放大器和功率放大器的区别)

射频放大器是一种电子电路&#xff0c;用于将输入信号增强到足够高的电平以驱动射频输出负载。其原理和作用如下&#xff1a; 射频放大器的工作原理是利用晶体管的三极管效应&#xff0c;将输入信号放大到足够的电平以驱动输出负载。在射频放大器中&#xff0c;输入信号经过输入…

深入理解 python 虚拟机:破解核心魔法——反序列化 pyc 文件

深入理解 python 虚拟机&#xff1a;破解核心魔法——反序列化 pyc 文件 在前面的文章当中我们详细的对于 pyc 文件的结构进行了分析&#xff0c;pyc 文件主要有下面的四个部分组成&#xff1a;魔术、 Bite Filed 、修改日期和 Code Object 组成。在前面的文章当中我们已经对前…

Android NDK: 使用Python生成下载地址

文章目录 1. 目的2. NDK下载链接3. 生成链接的 Python 脚本4. Bonus: 生成表格的 Python 脚本 1. 目的 Android NDK 的 github wiki 中给出了部分历史版本 NDK 的下载地址&#xff0c;有些版本的下载地址并没有在网页中给出。实际上这些下载地址很有规律。本文给出具体的链接&…

无人水面艇声呐装备现状与发展趋势(水声功率放大器)

无人水面艇声呐装备是目前海洋探测和水下情报收集的重要工具&#xff0c;其发展趋势受到了国防、军事、海洋资源勘探等领域的广泛关注。本文将介绍当前无人水面艇声呐装备的现状以及未来的发展趋势。 一、现状 无人水面艇声呐装备主要应用于水下物体的探测和测量&#xff0c;其…

NDK OpenGL仿抖音极快极慢录制特效视频

NDK​系列之OpenGL仿抖音极快极慢录制特效视频&#xff0c;本节主要是在上一节OpenGL代码架构上增加极快极慢等特效的视频录制功能。 实现效果&#xff1a; 实现逻辑&#xff1a; 在上一节的特效效果的基础上&#xff0c;使用MediaCodec和自定义EGL&#xff0c;将效果视频录制…

CountDownLatch与Binder连接池

CountDownLatch与Binder连接池 CountDownLatch 如果现在有一个题,有5个数,这时候我想让这5个数同时都乘2,然后算出结果后再算它们的平均数 这时候就可以用CountDownLatch import java.util.concurrent.CountDownLatch; public class Example {public static void main(Stri…

总结853

学习目标&#xff1a; 月目标&#xff1a;5月&#xff08;张宇强化前10讲&#xff0c;背诵15篇短文&#xff0c;熟词僻义300词基础词&#xff09; 周目标&#xff1a;张宇强化前3讲并完成相应的习题并记录&#xff0c;英语背3篇文章并回诵 每日必复习&#xff08;5分钟&#…

leetcode(力扣)刷题笔记(c++)【下】

文章预览&#xff1a; 单调栈739. 每日温度496.下一个更大元素 I503. 下一个更大元素 II42. 接雨水84.柱状图中最大的矩形 额外题目1365.有多少小于当前数字的数字941. 有效的山脉数组1207. 独一无二的出现次数189. 轮转数组724. 寻找数组的中心下标922. 按奇偶排序数组 II 后续…

软考高级架构师笔记-3数据库

目录 1. 前言 & 更新2. 数据库基本概念3. E-R图与二维表4. 约束、范式5. 数据库新技术1. 前言 & 更新 前文回顾: 软考高级架构师笔记-1计算机硬件软考高级架构师笔记-2计算机软件(操作系统)本章考情: 数据库章节都会考3-5分左右,第二版教材上对应2.3.3和6,主要考…

软考A计划-真题-分类精讲汇总-第十四章(数据流图)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Python每日一练(20230517) 最大连续1的个数 I\II\III

目录 1. 最大连续1的个数 I Max Consecutive Ones &#x1f31f; 2. 最大连续1的个数 II Max Consecutive Ones &#x1f31f;&#x1f31f; 3. 最大连续1的个数 III Max Consecutive Ones &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; G…

RabbitMQ养成记 (5. MQ的topics模式)

主题模式 Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符&#xff01; Routingkey 一般都是有一个或多个单词组成&#xff0c;多个单词之间以”.”分割&a…

【夜莺(Flashcat)V6监控】2.夜莺告警相关: 多服务器多业务配置

介绍 本章侧重点是应用&#xff0c;根据大家不同业务、服务器部署众多等等&#xff1b;根据不同团队&#xff0c;不同业务进行划分&#xff1b;方便不同的团队负责自己职责内的工作&#xff1b; 比如我们场景如下&#xff1a; 三块业务&#xff1a;人工智能、医药、团购&…