C语言从入门到精通 第十二章(程序的编译及链接)
写在前面:
- 本系列专栏主要介绍C语言的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程。
- 除了参考下面的链接教程以外,笔者还参考了其它的一些C语言教材,笔者认为重要的部分大多都会用粗体标注(未被标注出的部分可能全是重点,可根据相关部分的示例代码量和注释量判断,或者根据实际经验判断)。
- 如有错漏欢迎指出。
参考教程: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;
}
相关文章:

C语言从入门到精通 第十二章(程序的编译及链接)
写在前面: 本系列专栏主要介绍C语言的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程。除了参考下面的链接教程以外,笔者还参考了其它的一些C语言教材,笔者认为重要的部分大多都会用粗体标注…...

即插即用篇 | YOLOv8 引入 ParNetAttention 注意力机制 | 《NON-DEEP NETWORKS》
论文名称:《NON-DEEP NETWORKS》 论文地址:https://arxiv.org/pdf/2110.07641.pdf 代码地址:https://github.com/imankgoyal/NonDeepNetworks 文章目录 1 原理2 源代码3 添加方式4 模型 yaml 文件template-backbone.yamltemplate-small.yamltemplate-large.yaml...

基于51单片机的数字频率计设计
基于51单片机的数字频率计设计 摘要: 本文深入探讨了基于51单片机的数字频率计设计方案与实践。该设计方案不仅实现了对输入信号频率的高精度测量,还通过直观的数字显示提供了便捷的读取方式。在电子测量领域,频率作为核心参数之一,其准确测量对于确保通信、音频处理、控…...

20240307-1-前端开发校招面试问题整理JavaScript
前端开发校招面试问题整理【1】——JavaScript 1、JavaScript 基础 Q:介绍 js 的基本数据类型? 基本类型(值类型):String,Number,Boolean,Null,Undefined,S…...

1.3 数据库系统的结构
目录 1.3.1 数据库系统模式的概念 1.3.2 数据库系统的三级模式结构 1. 模式 2. 外模式 3.内模式(也称存储模式) 1.3.3 数据库的二级映像功能与数据独立性 1.外模式/模式映像 2.模式/内模式映像 1.3.4 总结 模式 内模式…...

【Springer出版 · EI检索】| 第二届先进无人飞行系统国际会议(ICAUAS 2024)
会议简介 Brief Introduction 2024年第二届先进无人飞行系统国际会议(ICAUAS 2024) 会议时间:2024年6月14日-16日 召开地点:中国南昌 大会官网:ICAUAS 2024-2024 2nd International Conference on Advanced Unmanned Aerial Systems2024 2nd …...

RocketMQ快速入门_2. rocketmq 的应用场景、与其他mq的差异
0. 引言 之前我们讲解过rabbitMQ,本期我们将进入吞吐量更加强大的rocketMQ的学习。 1. 基础概念 如果你是刚接触MQ的同学,还不清楚消息队列的基础概念的,可以参考我之前这篇文章: https://wu55555.blog.csdn.net/article/deta…...

【Azure 架构师学习笔记】- Azure Private Endpoint
本文属于【Azure 架构师学习笔记】系列。 前言 公有云的其中一个特点是默认允许公网访问, 这就对企业环境带来风险,也是很多年前企业对公有云抵触的其中一个原因,现在这类问题已经很少,因为有了很多技术来确保云上的资源被安全地…...

开发知识点-Python-爬虫
爬虫 scrapybeautifulsoupfind_all find祖先/父节点兄弟节点nextpreviousCSS选择器属性值 attrsselect 后 class 正则使用字符串来描述、匹配一系列符合某个规则的字符串组成元字符使用grep匹配正则组与捕获断言与标记条件匹配正则表达式的标志 特定中文 匹配 scrapy scrapy内…...

如何修复eutil.dll文件,eutil.dll下载安装教程
在我们使用计算机的时候,偶尔会遭遇一些技术问题,其中一个比较常见的问题就是出现了"丢失eutil.dll文件"的提示。当我们的电脑告诉我们缺少了eutil.dll文件时,常常是因为某些程序无法找到这个文件而导致了程序的运行异常。那我们应…...

虾皮、lazada店铺运营攻略,如何搭建高效、稳定的自养号测评系统
随着电子商务的蓬勃发展,越来越多的人选择在虾皮这样的电商平台上开设店铺,以实现创业梦想。但如何在众多店铺中脱颖而出,成为消费者的首选?本文将为您详细解答“怎么样做好虾皮店铺”,并提供一些实用的运营建议。 一、怎么样做…...

《TCP/IP详解 卷一》第13章 TCP连接管理
目录 13.1 引言 13.2 TCP连接的建立与终止 13.2.1 TCP半关闭 13.2.2 同时打开与关闭 13.2.3 初始序列号 13.2.4 例子 13.2.5 连接建立超时 13.2.6 连接与转换器 13.3 TCP 选项 13.3.1 最大段大小选项 13.3.2 选择确认选项 13.3.3 窗口缩放选项 13.3.4 时间戳选项与…...

许多人可能还不了解这个信息差:美赛的第一批 EI 已经录用,不用再犹豫啦
格局打开,美赛论文转学术论文发表 🚀🚀 各位同学,美赛已经结束了一段时间,你们是否还在焦急地等待最终成绩的公布?一些有远见的同学已经提前收到了一份喜讯:他们的美赛论文已被转化为学术论文并…...

DFS回溯-经典全排列问题(力扣)
前言 对于全排列问题,常用的做法是设置一个vis数组来确定位置i上的数字是否被访问,因为是全排列问题,所以不同的顺序也是不一样的排列,因此每次都是从起点开始询问**(注意起点到底是0还是1)** 46全排列(最简单的模板) class So…...

如何在Windows上使用Docker,搭建一款实用的个人IT工具箱It- Tools
文章目录 1. 使用Docker本地部署it-tools2. 本地访问it-tools3. 安装cpolar内网穿透4. 固定it-tools公网地址 本篇文章将介绍如何在Windows上使用Docker本地部署IT- Tools,并且同样可以结合cpolar实现公网访问。 在前一篇文章中我们讲解了如何在Linux中使用Docker搭…...

Linux运维_Bash脚本_编译安装ncurses-5.6
Linux运维_Bash脚本_编译安装ncurses-5.6 Bash (Bourne Again Shell) 是一个解释器,负责处理 Unix 系统命令行上的命令。它是由 Brian Fox 编写的免费软件,并于 1989 年发布的免费软件,作为 Sh (Bourne Shell) 的替代品。 您可以在 Linux 和…...

pip install和conda install的区别
先说结果:日常对于python的学习和简单项目推荐使用pip安装,效率更高,也不会有很多依赖问题。 首先,无论是conda还是pip,它们都属于包管理工具,直白点来说就是用来下载东西的。 二者的区别主要有以下几点&…...

实现video视频缓存
方法一 要实现视频被播放过后本地有缓存,下次播放无需网络即可播放,你可以利用浏览器的本地存储功能(如localStorage或IndexedDB)来实现。 你可以在视频播放结束时,将视频的URL以及相关信息存储在本地存储中。然后&a…...

Jmeter事务控制器实战
在性能测试工作中,我们往往只测试业务功能相关主要接口的数据请求和返回。然而实际上用户在使用web应用时,可能会加载诸多资源:htmldom、cssdom、javaScript、ajax请求、图片等。 从打开一个页面到界面渲染完成需要一定的加载时间࿰…...

S4---FPGA-K7板级原理图硬件实战
视频链接 FPGA-K7板级系统硬件实战01_哔哩哔哩_bilibili FPGA-K7板级原理图硬件实战 基于XC7K325TFFG900的FPGA硬件实战框图 基于XILINX 的KINTEX-7 芯片XC7K325FPGA的硬件平台,FPGA 开发板挂载了4 片512MB 的高速DDR3 SDRAM 芯片,另外板上带有一个SODIM…...

某些微信浏览器(比如小米手机mix2 8.0,Android 6:ZTE 7 max)input标签file属性,无法选中图片或者调用相机
1.初始化wxConfig (appId,timestamp,nonceStr,signatur,jsApiList) window.localStorage.setItem(currentUrl, window.location.href); 2.wx.checkJsApi({jsApiList: [chooseImage] // 需要检测的JS接口列表success: function(res) {// 以键值对的形式返回,可用的ap…...

python网络爬虫技术-mysql-5.6.39 安装
一、下载安装文件 到 MySQL官网 下载 mysql-5.6.39 压缩包链接:链接:https://pan.baidu.com/s/14e05FMhcWE8bvvStwyevNQ 提取码:1234 参考安装教程...

Projection head与使用例子
概念介绍 在深度学习中,Projection head是一种用于提取特征或表征的网络结构。它通常是一个或多个全连接层,将输入的高维特征映射到一个低维的向量空间,以便于进行后续的任务,如对比学习、聚类、分类等。 Projection head的使用…...

2024年新版CMS内容管理使用,不用回退老版本 使用最新小程序云开发cms内容模型
一,问题描述 最近越来越多的同学找石头哥,说cms用不了,其实是小程序官方最近又搞大动作了,偷偷的升级的云开发cms(内容管理)以下都称cms,不升级不要紧,这一升级,就导致我…...

MySql--死锁
一、什么是mysql死锁? MySQL中的死锁是指多个事务同时请求对同一资源进行操作(读或写),并且由于资源被互斥地锁定,导致彼此无法继续进行。当发生死锁时,MySQL会自动选择其中一个事务作为死锁的牺牲者,回滚该事务,并释放锁定的资源,从而解除死锁。 以下是一些处理MyS…...

【自然语言处理六-最重要的模型-transformer-上】
自然语言处理六-最重要的模型-transformer-上 什么是transformer模型transformer 模型在自然语言处理领域的应用transformer 架构encoderinput处理部分(词嵌入和postional encoding)attention部分addNorm Feedforward & add && NormFeedforw…...

开发一个带有Servlet的webapp(重点)
【具体步骤如下】 ①在webapps目录下新建一个目录,起名crm(这个crm就是webapp的名字)。当然,也可以是其他目录,名字自拟 注意:crm就是这个webapp的根 ②在webapp的根下新建一个目录:WEB…...

根据xlsx文件第一列的网址爬虫
seleniumXpath 在与该ipynb文件同文件下新增一个111.xlsx,第一列放一堆需要爬虫的同样式网页 然后使用seleniumXpath爬虫 from selenium import webdriver from selenium.webdriver.common.by import By import openpyxl import timedef crawl_data(driver, url)…...

【Linux】 yum —— Linux 的软件包管理器
Linux 的软件包管理器 yum yum 是什么什么是软件包查看软件包 yum 命令行工具yum 配置文件yum 凭什么可以支持下载呢?yum 生态yum 社区yum 的故障排除和资源支持yum 的持续集成和持续交付 yum 是什么 Yum(Yellowdog Updater Modified)是一个…...

函数柯里化(function currying)及部分求值
函数柯里化(function currying) currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保…...