C语言从入门到精通 第十二章(程序的编译及链接)

  写在前面:

  1. 本系列专栏主要介绍C语言的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程。
  2. 除了参考下面的链接教程以外,笔者还参考了其它的一些C语言教材,笔者认为重要的部分大多都会用粗体标注(未被标注出的部分可能全是重点,可根据相关部分的示例代码量和注释量判断,或者根据实际经验判断)。
  3. 如有错漏欢迎指出。

参考教程:C语言程序设计从入门到进阶【比特鹏哥c语言2024完整版视频教程】(c语言基础入门c语言软件安装C语言指针c语言考研C语言专升本C语言期末计算机二级C语言c语言_哔哩哔哩_bilibili

一、程序环境及编译+链接

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

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

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

(2)第2种是执行环境,它用于实际执行代码。

2、翻译环境下的工作

(1)组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code),每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。

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

3、编译本身分为几个阶段

(1)预处理(预编译):预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。(把#include包含的文件全部拷贝进文件,注释全部删除,#define全部代入替换)

(2)编译:经过语法分析、词法分析、语义分析和符号汇总(把在全局中的符号汇总,比如全局变量名及函数名)把C代码转换成汇编代码,编译完成之后就停下来,结果保存在test.s中。

(3)汇编:汇编完成之后就停下来,结果保存在test.o中。(把汇编代码转换为二进制/机器指令,利用编译阶段符号汇总的结果形成符号表)

(4)最后还要进行链接,包括合并段表以及符号表的合并和符号表的重定位(如上图,sum有两个地址,重定位会在其中选取一个有效的地址,然后进行合并),然后生成可执行程序.elf。

4、程序执行的过程

(1)程序必须载入内存中。

①在有操作系统的环境中,一般这个由操作系统完成。

②在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

(2)程序的执行开始——调用main函数。

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

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

二、预处理(一)

1、C语言内置的预定义符号

__FILE__      //进行编译的源文件

__LINE__     //文件当前的行号

__DATE__    //文件被编译的日期

__TIME__    //文件被编译的时间

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

2、#define 定义标识符

(1)用#define指令指定一个符号名称(例如PI)代表一个常量(例如3.1415),这个常量就是符号常量,它不占内存,在预编译后这个符号就不存在了。

(2)经过#define的指定后,文件从该行开始所有的(PI)都代表3.1415,对程序进行编译前,预处理器先对PI进行处理,把代码中所有PI全部置换为3.1415,在预编译后,符号常量就全部变成了字面常量3.1415。

(3)“#define 定义标识符”的好处:

①含义清楚。

②在需要改变程序中多出用到的同一个常量时,能做到“一改全改”,非常方便。

(4)#define指令不止可以定义符号常量,它可以把一大段内容(比如一个函数的调用)用一个简短的标识符代替,对程序进行编译前,预处理器会将代码中出现过的这个简短的标识符全部替换成那一大段内容

(5)举例:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#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\tdate:%s\ttime:%s\n" ,\__FILE__,__LINE__ ,\__DATE__,__TIME__ )int main()
{DEBUG_PRINT;return 0;
}

3、#define 定义宏

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

        #define name( parament-list ) stuff

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

②参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

③stuff中的参数以及整个stuff最好都加括号,这是为了防止运算符优先级引发若干问题。

④在代码中使用定义的宏(函数)时,格式为“name(parament-list)”。

(2)举例:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define SQUARE(x) ((x) * (x))   //整个表达式以及每个参数最好都加括号int main()
{int a = 5;printf("%d\n", 10 * SQUARE(a + 1));//printf("%d\n", 10 * ((a + 1) * (a + 1)));return 0;
}

4、#define 的替换规则

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

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

(3)最后再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号,如果是,就重复上述处理过程。

(4)需要注意的是:

①宏参数和#define 定义中可以出现其他#define定义的变量,但是对于宏,不能出现递归。

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

5、#和##

(1)使用“#”可以把一个宏参数变成对应的字符串,具体方法是在stuff中的字符串参数的首部(双撇号内)添加一个“#”。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>#define PRINT(FORMAT, VALUE)(printf("the value of " #VALUE " is "FORMAT "\n", VALUE))int main()
{int i = 1;PRINT("%d", i + 3);return 0;
}

(2)使用“##”可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>#define ADD_TO_SUM(num, value) (value##num)int main()
{int sum = ADD_TO_SUM(5, 10);printf("%d\n", sum);//105return 0;
}

6、带副作用的宏参数

(1)当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预测的后果。

(2)举例:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )int main()
{int x = 5;int y = 8;int z = MAX(x++, y++);//z = ( (x++) > (y++) ? (x++) : (y++));printf("x=%d y=%d z=%d\n", x, y, z); //x=6 y=10 z=9return 0;
}

7、宏(函数)和普通函数的对比

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

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

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

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

②宏是没法调试的。

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

④宏可能会带来运算符优先级的问题,导致程容易出现错。

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

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num, type)\
(type *)malloc(num * sizeof(type))int main()
{//使用MALLOC(10, int);//类型作为参数//预处理器替换之后://(int *)malloc(10 * sizeof(int));return 0;
}

8、#undef

        这条指令用于移除一个宏定义,使用方式如下。

#undef NAME

        //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

三、预处理(二)

1、条件编译

(1)在编译一个程序的时候,如果要将一条语句(一组语句)编译或者放弃是很方便的,因为C语言有条件编译指令。

(2)调试性的代码(比如用printf输出一些中间变量)删除可惜,保留又碍事,这时就可以选择性地编译。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define __DEBUG__int main()
{int i = 0;int arr[10] = { 0 };for (i = 0; i < 10; i++){arr[i] = i;
#ifdef __DEBUG__printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 
#endif //__DEBUG__}return 0;
}

(3)常见的条件编译指令:

①单个分支的条件编译:

#if 常量表达式

//...

#endif

//常量表达式由预处理器求值,其逻辑和一般的if语句相同,表达式结果不为零即编译其下的内容

如:

#define __DEBUG__ 1

#if __DEBUG__

//..

#endif

②多个分支的条件编译:

#if 常量表达式

//...

#elif 常量表达式

//...

#else

//...

#endif

③判断是否被定义:

#if defined(symbol)

#ifdef symbol        //如果有用#define定义symbol

#if !defined(symbol)

#ifndef symbol       //如果没有用#define定义symbol(这条指令常用于防止头文件被重复包含)

//这四条指令都需接#endif

④嵌套指令:

#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       //每条条件编译指令都需要配对一个“#endif”

2、offsetof宏的实现

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stddef.h>  //使用offsetof需要包含此头文件//写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
#define OFFSETOF(type,member) (size_t)&(((type*)0)->member)struct S
{char c1;int i;char c2;
};
int main()
{struct S s = { 0 };printf("%d\n", OFFSETOF(struct S, c1));printf("%d\n", OFFSETOF(struct S, i));printf("%d\n", OFFSETOF(struct S, c2));return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Doris-数据分区

数据分区&#xff1a;即将大表划分为小表&#xff0c;数据分区主要有两个级别&#xff1a;Partition和Bucket&#xff08;Tablet&#xff09;。 Partition&#xff1a;逻辑分区&#xff0c;按照一定规则将表按照行进行划分&#xff0c;每个部分就是一个Partition。 Bucket&…

根据用户名称实现单点登录

一、参数格式 二、后端实现 Controller层 public class IAccessTokenLoginController extends BaseController {Autowiredprivate ISysUserService sysUserService;Autowiredprivate ISingleTokenServiceImpl tokenService;/*** 登录方法** return 结果*/PostMapping("/l…

AI智能分析网关V4智慧园区视频智能监管方案

一、背景需求分析 随着科技的不断发展&#xff0c;智慧园区建设已成为现代城市发展的重要方向。通过智能化技术提高园区的运营效率、降低成本、增强环境可持续性等具有重要作用。视频智能监管作为智慧园区安全管理体系的重要组成部分&#xff0c;对于提高园区的安全管理水平和…

手写简易操作系统(一)--环境配置

本专栏是我新开设的一个学术专栏&#xff0c;旨在全面介绍手写操作系统的相关内容。其中包括实模式向保护模式的过渡、锁机制、信号量操作、内存分配、硬盘驱动、文件系统、简单shell和管道等操作系统核心知识。该专栏旨在为有意开发自己操作系统的研究人员提供指导与帮助。作为…

阿里云服务器怎么使用?3分钟搭建网站教程2024新版

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网aliyunfuwuqi.com以搭建WordPress网站博客为例&#xff0c;来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流…

探索数据结构:单链表的实战指南

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty‘s blog 前言 在上一章节中我们讲解了数据结构中的顺序表&#xff0c;知道了顺序…

【校园导航小程序】2.0版本 静态/云开发项目 升级日志

演示视频 【校园导航小程序】2.0版本 静态/云开发项目 演示 首页 重做了首页&#xff0c;界面更加高效和美观 校园指南页 新增了 “校园指南” 功能&#xff0c;可以搜索和浏览校园生活指南 地图页 ①弃用路线规划插件&#xff0c;改用SDK开发包。可以无阻通过审核并发布…

吴恩达机器学习-可选实验室:特征工程和多项式回归(Feature Engineering and Polynomial Regression)

文章目录 目标工具特征工程和多项式回归概述多项式特征选择功能备用视图扩展功能复杂的功能 恭喜! 目标 在本实验中&#xff0c;你将:探索特征工程和多项式回归&#xff0c;它们允许您使用线性回归的机制来拟合非常复杂&#xff0c;甚至非常非线性的函数。 工具 您将利用在以…

【vue2基础教程】vue指令

文章目录 前言一、内容渲染指令1.1 v-text1.2 v-html1.3 v-show1.4 v-if1.5 v-else 与 v-else-if 二、事件绑定指令三、属性绑定指令总结 前言 Vue.js 是一款流行的 JavaScript 框架&#xff0c;广泛应用于构建交互性强、响应速度快的现代 Web 应用程序。Vue 指令是 Vue.js 中…

cf火线罗技鼠标宏最细教程(鬼跳,上箱,一键顺,usp速点,雷神三连发及压枪,AK火麒麟压枪.lua脚本)

一.前言 因为我发现火线的鼠标宏非常多&#xff0c;想着自己也有罗技鼠标&#xff0c;看能不能自己写一写让游玩的时候更方便操作一些&#xff0c;可能不一定有什么帮助&#xff0c;但也是一个学习的过程&#xff0c;下面就把我自己的心得和代码详细的记录下来&#xff0c;好多…

注意力机制(代码实现案例)

学习目标 了解什么是注意力计算规则以及常见的计算规则.了解什么是注意力机制及其作用.掌握注意力机制的实现步骤. 1 注意力机制介绍 1.1 注意力概念 我们观察事物时&#xff0c;之所以能够快速判断一种事物(当然允许判断是错误的), 是因为我们大脑能够很快把注意力放在事物…

C语言从入门到精通 第十一章(文件操作)

写在前面&#xff1a; 本系列专栏主要介绍C语言的相关知识&#xff0c;思路以下面的参考链接教程为主&#xff0c;大部分笔记也出自该教程。除了参考下面的链接教程以外&#xff0c;笔者还参考了其它的一些C语言教材&#xff0c;笔者认为重要的部分大多都会用粗体标注&#xf…

算法刷题day25:多路归并

目录 引言概念一、鱼塘钓鱼二、技能升级三、序列 引言 关于这个多路并归蓝桥杯考的不是很多&#xff0c;如果要出的话&#xff0c;可能模型都会差不多&#xff0c;因为不会出太难的题&#xff0c;难题基本上都是贪心、DP之类的&#xff0c;所以好好刷题刷熟练就行了&#xff0…

Vue事件处理:.passive修饰符与应用场景

.passive修饰符 passive这个修饰符会执行默认方法。你们可能会问&#xff0c;明明默认执行为什么会设置这样一个修饰符。这就要说一下这个修饰符的本意了。 浏览器只有等内核线程执行到事件监听器对应的JavaScript代码时&#xff0c;才能知道内部是否会调用preventDefa…

一次电脑感染Synaptics Pointing Device Driver病毒的经历,分享下经验

没想到作为使用电脑多年的老司机也会电脑中病毒&#xff0c;周末玩电脑的时候突然电脑很卡&#xff0c;然后自动重启&#xff0c;奇怪&#xff0c;之前没出现这个情况。 重启后电脑开机等了几十秒&#xff0c;打开任务管理器查看开机进程&#xff0c;果然发现有个Synaptics Po…

【计网】TCP协议安全与风险:深入探讨网络通信的基石

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 &#x1f310;前言 &#x1f512;正文 TCP (Transmission Control Protocol): UDP (User Datagram Protocol): HTTP (Hypertext Transfer …

【动态规划.3】[IOI1994]数字三角形 Number Triangles

题目 https://www.luogu.com.cn/problem/P1216 观察下面的数字金字塔。 写一个程序来查找从最高点到底部任意处结束的路径&#xff0c;使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。 7→3→8→7→5 的路径产生了最大权值。 分析 这是一个动态规划…

3.8 动态规划 背包问题

一.01背包 46. 携带研究材料&#xff08;第六期模拟笔试&#xff09; (kamacoder.com) 代码随想录 (programmercarl.com) 携带研究材料: 时间限制&#xff1a;5.000S 空间限制&#xff1a;128MB 题目描述: 小明是一位科学家&#xff0c;他需要参加一场重要的国际科学大会…

杨辉三角(附html,Python,c++杨辉三角代码)

1、前言 杨辉三角很早学编程就知道&#xff0c;但不知道杨辉是古人&#xff01; 一次偶然的机会&#xff0c;和杨辉三角对上了眼神——杨辉&#xff01;究竟你是怎么发现杨辉三角的呢&#xff1f; 于是经过了长达3个月锲而不舍的探究。 终究也没发现自己想要的最终结果。 …

【YOLO v5 v7 v8 v9小目标改进】AFPN 渐进式特征金字塔网络:解决多尺度特征融合中,信息在传递过程丢失

AFPN 渐进式特征金字塔网络&#xff1a;解决多尺度特征融合中&#xff0c;信息在传递过程丢失 提出背景AFPN 多尺度特征金字塔 非邻近层次的直接特征融合 自适应空间融合操作 小目标涨点YOLO v5 魔改YOLO v7 魔改YOLO v8 魔改YOLO v9 魔改 提出背景 论文&#xff1a;https:…