程序的编译(预处理操作)+链接

news2025/1/12 23:04:55

目录

前言:

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

 2. 详解编译+链接

翻译环境:

1.预处理:(文本操作)

2.编译:

3.汇编:

4.链接:

运行环境

预处理详解 :

#define的详解 

#define定义宏:

 #define的替换规则:

#和## 

带有副作用的宏参数

宏和函数的对比

条件编译:

 文件的包含

头文件被包含的方式

总结:


前言:

编译是将源代码转换为目标代码的过程,目标代码是二进制指令集,可以直接在计算机上执行。编译器将源代码逐行翻译成目标代码,常见的编译器有gcc、clang等。

链接是将编译器生成的目标代码合并成可执行文件的过程。当多个源文件进行编译后,会生成多个目标文件,链接器将这些目标文件合并成一个可执行文件,并处理各种符号表和重定向等问题,使得程序能够正常运行。常见的链接器有ld、lld等。

在编译时,编译器通常需要使用头文件和库文件。头文件是一系列函数和变量的声明,通常以.h为后缀,可以让编译器知道这些函数和变量的定义。库文件是一些二进制代码的集合,通常以.a或.so为后缀,包含了很多已经编译好的函数和变量的实现,可以加速编译和链接的过程。

编译和链接是程序开发过程中的重要步骤,它们决定了程序是否能够正常运行和性能是否达到要求。

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

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

1.翻译环境,在这个环境中源代码被转换为可执行的机器指令。

2.执行环境,用于实际执行代码。

程序的翻译环境是指将程序源代码转换为可执行文件的过程中使用的工具、程序和规则,也称为编译器。翻译环境主要包括以下几个方面:

  1. 编辑器:用于编辑程序代码的工具,如记事本、Sublime Text、Visual Studio等。

  2. 预处理器:处理代码中的预处理指令,如#include、#define等。

  3. 编译器:将源代码转换为目标代码的程序,如GCC、Clang等。

  4. 链接器:将目标代码与库文件链接成可执行文件,如ld、MSVC Linker等。

程序的执行环境是指程序在运行时所依赖的系统环境,包括硬件平台、操作系统、运行时库等。执行环境主要包括以下几个方面:

  1. 操作系统:提供程序运行所需的系统调用和资源管理,如Windows、Linux、macOS等。

  2. 运行时库:提供程序所需的函数和服务,如C++标准库、OpenGL库等。

  3. 硬件平台:提供程序运行所需的处理器、内存、输入输出设备等,如x86、ARM等。

  4. 外部设备:提供程序所需的输入输出接口,如鼠标、键盘、显示器等。

 2. 详解编译+链接

翻译环境:

1.组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。

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

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

windows标准环境下生成的目标文件是xxx.obj

linux环境下生产的目标文件是xxx.o

连接器同事回应如标准C函数库中任何被该程序调用的函数,而且可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

1.预处理:(文本操作)

1.将程序中的注释转换为空格。

2.头文件的包含。

3.#define符号的替换。

2.编译:

将C语言代码翻译成汇编代码。

1.词法分析

2.语法分析

3.语义分析

4.符号汇总

3.汇编:

把汇编代码翻译成二进制的指令,生成目标文件和符号表。

4.链接:

链接目标文件和链接库生成可执行程序(二进制程序)

1.合并段表

2.符号表的合并和重定位

运行环境

程序执行的过程:

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

2. 程序的执行便开始。接着便调用main函数。

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

4. 终止程序。正常终止main函数;也有可能是意外终止。

预处理详解 :

1.预定义符号:

__FILE__ 进行编译的源文件

__LINE__文件当前行号

__DATE__文件被编译的日期

__TIME__文件被编译的时间

__STDC__如果遵循ANSIC,其值为1,否则不存在。

以上均是语言内置的。

举例:

#define _CRT_SECURE_NO_WARNINGS  1
#include<stdio.h>
int main()
{
	printf("file:%s\nline:%d\ndate:%s\ntime:%s\n",
             __FILE__, 
             __LINE__,
             __DATE__,
             __TIME__);
	return 0;
}

输出结果:

#define的详解 

语法: #define name stuff

是一种预处理指令

内容包括:

#define定义常量

#define定义宏

在我们之前实现通讯录,扫雷和三子棋中,都运用了#define定义一些变量,这些就是#define定义常量。

博客可以参考:

运用动态内存实现通讯录(增删查改+排序)-CSDN博客

C语言实现《扫雷》_无双@的博客-CSDN博客

C语言实现《三子棋》游戏-CSDN博客

#define定义宏:

它是一种预处理指令,用于将标识符与特定的文本替换关联起来。这允许您在代码中创建符号常量或简单的代码替换。#define的一般语法如下:

#define 宏名称 替换文本

宏名称是您要定义的标识符,替换文本是您希望在代码中使用该标识符时进行替换的文本。

下面是一个示例,演示如何使用#define定义宏:

#include <stdio.h> 
#define PI 3.14159265 
#define SQUARE(x) (x * x) 

int main() 
{ 
    double radius = 5.0; 
    double area = PI * SQUARE(radius);
    printf("圆的面积是:%lf\n", area);
    return 0;
}

在这个示例中,#define用于定义两个宏:PISQUARE(x)PI被定义为一个替换文本,每次在代码中出现PI时,它都会被替换为3.14159265。SQUARE(x)是一个宏函数,它接受一个参数x,并返回x的平方。

main函数中,我们使用这些宏来计算圆的面积,而不是直接使用硬编码的值。这样,如果需要更改精度或其他参数,只需更新宏定义,而不必在整个代码中查找和更改每个引用。

注意:宏替换是简单的文本替换,没有类型检查,因此要谨慎使用宏,确保替换文本不会导致不希望的副作用。

1.宏参数和#define定义中可以出现其它#define定义的符号,但是对于宏,不能出现递归。

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

这里要注意的是,我们在定义宏时,最好对每一个变量进行括号操作。

代码如下:

#define SQUARE(X) (X*X)

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

 我们预期运行printf(“%d\n”,SQUARE(5+1))答案应当是36。

可是最终结果却是:11

因为实际上宏完成的操作是:

5+1 * 5+1 = 11

所以我们对以上代码进行修改,应当为:

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

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

 如此输出结果:

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

 #define的替换规则:

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

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先 被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。

如果是,就重复上 述处理过程。

#和## 

在编程和计算机领域中,"#" 和 "##" 通常用于表示不同的概念,具体取决于编程语言或上下文。以下是它们可能的用途和区别:

  1. "#"(井号):

    • 在许多编程语言中,"#" 通常用于表示注释。注释是程序中的文本,通常不会被编译或执行,用于解释代码的目的。例如,在Python中,您可以使用 "#" 来添加注释。
    • 在一些编程语言中,"#" 还可以用于预处理指令,例如在C和C++中,用于包含头文件。
  2. "##":

    • "##" 通常用于宏定义和预处理器指令中。预处理器是在代码编译之前执行的一种处理步骤,通常用于代码生成或替换。在C和C++等语言中,"##" 用于将两个标识符连接在一起,通常用于宏定义。
    • 例如,在C中,以下是一个使用"##" 的宏定义的示例:#define CONCAT(x, y) x##y 这将使 CONCAT(a, b) 展开为 ab。#define CONCAT(x, y) x##y 这将使 CONCAT(a, b) 展开为 ab。

#define CONCAT(x, y) x##y 
这将使 CONCAT(a, b) 展开为 ab。

总之,"#" 通常用于注释和一些预处理指令,而 "##" 主要用于宏定义和预处理器中的标识符连接。具体用法和含义可能会因编程语言而异,因此请查阅特定编程语言的文档以获取详细信息。

举例:

#define PRINT(n, format) printf("the value of "#n" is "format"\n",n)

int main()
{
	int a = 10;
	PRINT(a,"%d");
	float f = 4.5f;
	PRINT(f, "%f");
	return 0;
}

以上是关于#号的

关于##:把位于它两边的符号合成一个符号

例子:

#define CAT(v,n) v##n
int main()
{
	int value10 = 100;
	printf("%d\n", CAT(value, 10));
	return 0;
}

 输出结果:

带有副作用的宏参数

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

例子:

#define MAX(x,y) ((x)>(y)?(x):(y))

int main()
{
	int a = 3;
	int b = 5;
	int m = MAX(a++, b++);
	//int m = MAX(((a++)>(b++)?(a++):(b++)));
	printf("%d\n", m);
	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
};

我们预期的输出结果应当为:

5

3

5

可是最终的输出结果:

 

这个的原因好理解,在这里我不做过多的赘述。

因此带有副作用的参数,会对程序最后的值产生较大的影响。

宏和函数的对比

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

2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

当然和宏相比函数也有劣势的地方:

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

2. 宏是没法调试的。

3. 宏由于类型无关,也就不够严谨。

4. 宏可能会带来运算符优先级的问题,导致程容易出现错。 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

为了区分函数和宏,

我们在日后写宏和函数时,应当运用全部大写写宏。

函数名则不需要全部大写。

#undef是移除一个宏定义。

条件编译:

条件编译是一种编译技术,它可以在编译代码时根据指定的条件选择性地包含或排除某些代码块。条件编译通常使用预处理指令,在代码编译之前进行处理。这些指令可以根据定义的条件来判断是否编译某些代码,从而实现对不同操作系统、硬件平台或其他环境的适配,或者实现对不同版本或功能的控制。常见的条件编译指令包括 #ifdef、#ifndef、#if、#else、#elif 和 #endif 等。条件编译可以帮助程序员实现更好的程序控制和代码重用。

下面是一个代码示例,其中使用了条件编译指令来控制编译器在编译时包含哪些代码:

#include <stdio.h>

#define DEBUG_MODE 1

int main() {
    int x = 6;
    int y = 2;

    #ifdef DEBUG_MODE
        printf("Debug mode is ON\n");
    #endif

    #ifndef DEBUG_MODE
        printf("Debug mode is OFF\n");
    #endif

    #if DEBUG_MODE
        printf("x = %d, y = %d\n", x, y);
    #endif

    #if defined(DEBUG_MODE) && DEBUG_MODE == 1
        printf("The sum of x and y is %d\n", x + y);
    #endif

    return 0;
}

在上面的代码中,我们使用了 #ifdef#ifndef,和 #if 等条件编译指令来控制编译器在编译时是否包含哪些代码。在 main 函数中,我们使用了 #ifdef 指令来检查 DEBUG_MODE 宏是否已经定义,如果是,就会输出 "Debug mode is ON";如果 DEBUG_MODE 没有被定义,我们就使用 #ifndef 指令来输出 "Debug mode is OFF"。另外,我们还使用了 #if#endif 指令来控制是否输出变量 xy 的值,以及它们的和。#if 指令可以接受一个表达式,只有当表达式为真时,才会编译所包含的代码。在这个例子中,我们使用了 defined 运算符来检查 DEBUG_MODE 是否已经被定义,并将其与 1 进行比较。如果两者相等,我们就会输出变量 xy 的和。

这个例子中,条件编译指令可以让我们根据需要选择需要编译的代码。如果 DEBUG_MODE 宏已经被定义,我们就会得到更多的输出信息,从而方便调试程序。否则,我们只会得到必要的输出信息,节省了程序的运行时间。

 文件的包含
 

 我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方 一样。 这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。 这样一个源文件被包含10次,那就实际被编译10次。

头文件被包含的方式

本地文件包含

#include"XXX.h"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 准位置查找头文件。 如果找不到就提示编译错误。

库文件包含

#include<XXXX.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。 这样是不是可以说,对于库文件也可以使用 “” 的形式包含?

答案是肯定的,可以。

但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

嵌套文件包含是指在一个文件中包含了另一个文件,而被包含的文件中又包含了另一个文件,以此类推,形成了多层的文件包含关系。

例如,假设有一个文件A.php,其中包含了文件B.php,而文件B.php又包含了文件C.php,那么就形成了三层文件包含关系,即A.php -> B.php -> C.php。

嵌套文件包含的好处是可以实现代码的复用,减少重复的代码编写,提高开发效率。但是,如果嵌套层数太多,会导致代码的可读性变差,也可能会影响性能,因为每次文件包含都会增加文件读取和解析的开销。因此,在实际开发中,需要根据具体情况来决定是否使用嵌套文件包含。

为了避免头文件的重复引入,我们可以运用头文件

#pragma once

总结:

本文对C语言程序的编译和链接以及之中的预处理进行了初步的了解。

学习完后可以复习一下,并对其中进行精读。

记住

做而言不如起而行。

Action speak louder than words

以下是本文的代码:

Pretreatment_CSDN/Pretreatment_CSDN/test.c · 无双/test_c_with_X1 - Gitee.com

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

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

相关文章

Hash Join(PostgreSQL 14 Internals翻译版)

一阶段哈希连接&#xff08;One-Pass Hash Joins&#xff09; 散列连接使用预构建的散列表搜索匹配的行。下面是一个使用这种连接的计划的例子&#xff1a; 在第一阶段&#xff0c;哈希连接节点1调用哈希节点2&#xff0c;哈希节点2从其子节点提取整个内部行集&#xff0c;并将…

Python 面向对象初步

目录 1 面向对象和面向过程区别1.1 面向过程(Procedure Oriented)思维1.2 面向对象(Object Oriented)思维1.3 面向对象思考方式1.4 面向对象和面向过程的总结 2 对象的进化3 类的定义4 __init__构造方法和__new__方法5 实例属性和实例方法5.1 实例属性5.2 实例方法5.2.1 实例对…

【学术】知云文献及划词翻译软件(XTranslator)的安装及使用

文章目录 一、知云文献翻译1.1 知云文献翻译是什么1.2 知云文献翻译下载地址1.3 知云文献翻译安装1.4 知云文献翻译使用1.4.1 使用方法1.4.2 解除限制1.4.3 软件特点1.4.4 翻译PDF 1.5 Windows版使用文档1.6 解锁所有翻译引擎 二、知云划词翻译(Xtranslator)2.1 知云划词翻译(X…

C# Winform编程(5)菜单和菜单组件

菜单和菜单组件 添加菜单编辑菜单 添加菜单 将MenuStrip控件拖拽到Form窗体顶部添加菜单 编辑菜单 添加菜单项&#xff0c;编辑菜单属性等功能。 右键单击已添加的菜单项可以弹出右键菜单&#xff1a; 可以设置菜单图标&#xff0c;使能菜单&#xff0c;显示快捷键、转换菜…

提高三维模型数据的几何坐标精度需要采取方法浅析

提高三维模型数据的几何坐标精度需要采取方法浅析 要提高倾斜摄影三维模型数据的几何坐标精度&#xff0c;可以采取以下方法&#xff1a; 选择合适的倾斜角度&#xff1a;倾斜角度对于几何坐标精度具有重要影响。过小的倾斜角度可能导致图像中特征点不足以提供准确的位置信息&…

10数据库-基础

四、数据库 15、MySQL 数据库优化 SQL优化 mysql优化 一、避免不走索引的场景尽量避免在字段开头模糊查询&#xff0c;会导致数据库引擎放弃索引进行全表扫描。尽量避免使用not in&#xff0c;会导致引擎走全表扫描。尽量避免使用 or&#xff0c;会导致数据库引擎放弃索引进行…

[opencv]图像和特征点旋转

本来说这是很简单的一个内容&#xff0c;图像旋转只需要使用opencv中自带的旋转函数即可完成&#xff0c;但是最近在做特征点旋转的时候发现使用内置rotate函数给图像旋转90度&#xff0c;再用getRotationMatrix2D得出的旋转矩阵对特征点旋转&#xff0c;画出来的特征点位置全部…

零基础学习HTML5

1. 使用软件 vscode 谷歌浏览器 vscode下载地址&#xff1a;https://code.visualstudio.com/ 谷歌可以使用360软件管家安装 2. 安装插件 在vscode中安装插件&#xff1a;open in browser&#xff0c;点击Extensions后搜索对应插件名然后点击安装Install 安装完成后可在htm…

【LeetCode】543. 二叉树的直径

543. 二叉树的直径&#xff08;简单&#xff09; 思路 对于任一结点&#xff0c;以此结点为根的diameter就可以表示为左子树高度 右子树高度&#xff0c;而二叉树的diameter就是所有结点为根的diameter中最大的那个。因此&#xff0c;变量 maxLen 用来保存当前遍历过的节点的…

phpstudy_2016-2018_rce_backdoor 漏洞复现

phpstudy_2016-2018_rce_backdoor 漏洞复现 Remote Command Execute 打开 bp 打开代理浏览器 访问 php 页面 回到 bp 查看 http 历史&#xff0c;找到刚刚访问的 php 页面 发送到 Repeater 转到 Repeater php 页面请求内容加 Accept-Charset: 修改 Accept-Encodi…

Comsol电磁铁仿真

简介 Comsol是一款多物理场仿真软件&#xff0c;可以完成固体力学、流体力学、传热学和电磁学的仿真。本文将介绍使用Comsol完成电磁铁仿真的主要流程&#xff0c;计算铁芯的受力。 步骤 1.建立模型并设置材料 建立二维旋转对称模型&#xff0c;绿色为动铁&#xff0c;材料…

STM32F103外部晶振8MHZ改为16MHz的使用

STM32F103外部晶振8MHZ改为16MHz的使用 目录 STM32F103外部晶振8MHZ改为16MHz的使用前言一、修改标准函数库的方法1、stm32f10x.h修改HSE_VALUE2 、system stm32f10x.c的SetSysClockTo72&#xff08;&#xff09;函数修改3、不同晶振的统一配置的方式4 、时间晶振修改5、修改Ta…

基于STM32F407的FreeRTOS学习笔记(1)——环境搭建

以前使用STM32单片机一直停留在逻辑开发以及前后台系统&#xff0c;而真正被广泛使用的则是RTOS。 前后台系统则是我们常用的&#xff0c;使用一个主循环许多的调用函数这些构成了后系统&#xff0c;利用中断进行异常处理则是前系统。 而RTOS则是将任务按照优先级排列&#xf…

怎么压缩图片?图片过大这样压缩变小

在日常生活中&#xff0c;我们常常会遇到需要上传或发送图片的情况&#xff0c;然而&#xff0c;很多时候图片的大小会成为问题&#xff0c;因为过大的图片可能会导致传输速度变慢&#xff0c;甚至无法上传。那么&#xff0c;如何将这些过大的图片压缩变小呢&#xff1f; 一、嗨…

【C++进阶之路】类型转换

文章目录 类型转换1.C语言的类型转换1.1整形提升1.2算术转换1.3强制类型转换 2.C类型转换2.1static_cast2.2reinterpret_cast2.3const_cast2.3dynamic_cast 总结 类型转换 1.C语言的类型转换 1.1整形提升 在写顺序表的插入函数时&#xff0c;我们的接口实现是这样的&#xf…

Confluence 自定义展示页面

1. 概述 Confluence 作为知识库可通过JS脚本方式&#xff0c;根据登录用户或用户组进行前端页面的自定义 2. 实现方式 Confluence →管理→自定义HTML 嵌入对应JS脚本&#xff0c;示例如下 <script type"text/javascript">jQuery(#footer).html(<div>…

UE5 Python脚本自动化Sequence Key帧

前言 码上1024了&#xff0c;给大家分享一个UE5的脚本小功能&#xff0c;UE5中Sequence动态Key功能&#xff0c;这样我们就可以根据我们的数据动态更新了&#xff0c;非常实用&#xff0c;适合刚入门或者小白&#xff0c;接下来我就把整个过程分享给大家。 过程 新建一个工程…

E049-论坛漏洞分析及利用-针对bwapp进行web渗透测试的探索

课程名称&#xff1a; E049-论坛漏洞分析及利用-针对bwapp进行web渗透测试的探索 课程分类&#xff1a; 论坛漏洞分析及利用 --------------------------------------------------------------------------------------------------------------------------------- 实验等…

用一段爬虫代码爬取高音质音频示例

以下是一个使用Reachability库和Objective-C编写的爬虫程序&#xff0c;用于爬取高音质的免费音频。通过https://www.duoip.cn/get_proxy的代码示例完美抓取数据。 #import <Foundation/Foundation.h> #import <Reachability/Reachability.h>interface AudioCrawle…

vue3学习(八)--- 组件相关

文章目录 全局组件批量注册全局组件 局部组件递归组件组件定义名称方式1.增加一个script 通过 export 添加name2.直接使用文件名当组件名3.使用插件 unplugin-vue-define-options 动态组件异步组件 一个 Vue 组件在使用前需要先被“注册”&#xff0c;这样 Vue 才能在渲染模板时…