C语言 编译和链接

news2024/11/16 9:57:41

C语言 编译和链接

  • 引言
    • 翻译环境
    • 运行环境
    • 声明
  • 一、预定义符号
  • 二、#define 符号
    • 1. #define 定义标识符
    • 2. #define 定义宏
      • 宏带来的陷阱
      • 宏的两个特殊的使用场景
        • ① 使用 #,把一个宏参数变成对应的字符串
        • ② 使用 ##,将两个宏参数合并成一个符号
      • 宏参数的使用
    • 3. #define 定义宏和函数的区别
    • 4. #undef
  • 三、头文件包含
    • 嵌套头文件

引言

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

第一种是翻译环境,在这个环境中,源代码被转换为可执行的二进制指令。
翻译环境即我们日常使用编译器,将一个 " test.c " 的文件最终变成一个 " text.exe " 的可执行文件的一个过程。

第二种是运行环境,它用于实际执行代码。
运行环境一般是由操作系统对 " test.exe " 可执行文件进行解析执行的结果。

翻译环境

一图说明翻译环境 (C语言 源程序转换成可执行文件的过程):

1-1

运行环境

程序执行的过程:

① 一般来说,程序先是被操作系统载入到内存中。在独立的环境中,程序的载入也可能是通过可执行代码置入只读内存来完成。

② 程序运行开始,接着便调用 main 函数。

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

④ 终止程序。操作系统正常终止 main 函数,也有可能是意外终止。

声明

由于翻译环境中的编译、汇编涉及到过多的汇编代码知识。运行环境涉及到了的操作系统的知识。所以它们不在本篇博客讨论范围内,那么本篇博客的后续内容,我将只展开讨论 C语言 预编译的过程和其对应的一些细节。

一、预定义符号

__FILE__ 	// 进行编译的源文件
__LINE__ 	// 文件当前的行号
__DATE__ 	// 文件被编译的日期
__TIME__ 	// 文件被编译的时间
__STDC__ 	// 如果编译器遵循 ANSI C,其值为 1,否则未定义

二、#define 符号

1. #define 定义标识符

#include <stdio.h>
#define MAX 100
#define STR "hello world"

int main() {

	int n = MAX;
	// 预处理后变成:int n = 100;
	char arr[] = STR;
	// 预处理后变成:char arr[] = "hello world";

	printf("%d\n", n);
	printf("%s\n", arr);

	return 0;
}

// 输出结果:
// 100
// hello world

2. #define 定义宏

#include <stdio.h>
#define MAX(x,y) (x>y?x:y)

int main() {

	int a = 10;
	int b = 20;

	int n = MAX(a, b);
	// 预处理后变成:int n = (a>b?a:b);

	printf("%d\n", n); 

	return 0;
}

// 输出结果:20

宏带来的陷阱

程序清单1:

#include <stdio.h>
#define SQL(x) x*x

int main() {

	int n = 9;
	int result1 = SQL(n);
	// 预处理后变成:result1 = n*n;

	int result2 = SQL(n + 1);
	// 预处理后变成:int result2 = n+1*n+1;

	printf("%d\n", result1 );
	printf("%d\n", result2);

	return 0;
}

// 输出结果:
// 81
// 19

从上面的第二个输出结果来看,宏带来了运算符优先问题。由于 #define 在定义宏的时候,是直接对参数进行替换的。所以我们第二个预期为 " 100 " 的结果,最终变成了 19.

程序清单2:

我们可以利用加括号的方式,来解决优先级的运算符的问题。

#include <stdio.h>
#define SQL(x) ((x)*(x))

int main() {

	int n = 9;
	int result1 = SQL(n);
	// int result1 = ((n)*(n));

	int result2 = SQL(n + 1);
	// int result2 = ((n+1)*(n+1));

	printf("%d\n", result1);
	printf("%d\n", result2);

	return 0;
}

// 输出结果:
// 81
// 100

宏的两个特殊的使用场景

① 使用 #,把一个宏参数变成对应的字符串

#include <stdio.h>
#define PRINT(N) printf("the value of "#N" is %d\n", N)

int main() {

	int a = 10;
	PRINT(a);
	// 预处理后变成:printf("the value of ""a"" is %d\n", a);

	int b = 20;
	PRINT(b);
	// 预处理后变成:printf("the value of ""b"" is %d\n", b);

	return 0;
}

// 输出结果:
// the value of a is 10
// the value of b is 20

② 使用 ##,将两个宏参数合并成一个符号

#include <stdio.h>
#define MERGE(str1, str2) str1##str2

int main() {

	int class_room = 123;
	
	printf("%d\n", MERGE(class_, room));
	// 预处理后变成:printf("%d\n", class_room);

	return 0;
}

// 输出结果:123

宏参数的使用

#include <stdio.h>
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )

int main() {

	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);

	// int z = ((x++) > (y++) ? (x++) : (y++));		
	// ->  int z = ((5++) > (8++) ? (x++) : (y++));		// x=6, y=9
	// ->  int z = ((6) > (9) ? (x++) : (9++));	
	// ->  int z = 9++;									// x=6, y=10, z=9

	printf("x=%d y=%d z=%d\n", x, y, z);
	
	return 0;
}

// 输出结果:x=6 y=10 z=9

从上面的程序来看,原本我们是想求两个数中最大的一个数值,但由于自增所带来改变自身值的问题,导致宏参数在宏使用的时候,被修改了。这就间接地导致了上面的 x、y、z 的值也被修改了。

所以,我们可以得出结论: 当宏参数在宏的定义中出现超过一次的时候,如果宏参数影响本身的值,那么后续在使用这个宏的时候就可能出现不可预测的后果。按照当前的程序来看还好些,如果自增自减用的再复杂一些,你很难想象底层是怎么参与运算的。

3. #define 定义宏和函数的区别

#define 定义宏函数
执行速度更快相对宏的速度较慢,函数调用和函数返回都需要开销
调试宏不能够调试函数可以调试
递归宏不能够递归函数可以递归
命名一般宏的名字全部大写函数名不需要全部大写

4. #undef

#undef 用于移除一个宏定义,如下面的 error 注释 上面的一行,就是移除了 MAX 这个宏。之后再次使用的时候,就没有 MAX 这个定义了。

#include <stdio.h>
#define MAX(x,y) (x>y?x:y)
#define MIN -100

int main() {

	int a = 10;
	int b = 20;

	int n = MAX(a, b);
	// int n = (a>b?a:b);

	printf("%d\n", n);

	#undef MAX
	int n = MAX(a, b); 	// error

	return 0;
}

三、头文件包含

在引言的地方,我提到了 #include 头文件包含属于预编译的过程,它其实也是进行了相关的文本替换。但 C语言 的头文件分为两种,第一种是和库相关的库文件;第二种是本地文件包含。

如下面的程序:

注释1 为库文件包含,查找头文件的时候是直接去标准路径下去查找,如果找不到就提示编译错误。
注释2 为本地文件包含,查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。

#include <stdio.h>		// 1

#include "function.h"	// 2

嵌套头文件

#include "function.h"
#include "function.h"
#include "function.h"

如上所示,如果我们在程序中重复引入头文件,就会在预编译的情况下,带来重复的文本替换。例如上面引入了三个头文件,那么在预编译时,就会存在三个同样的文本替换,这也被称为嵌套了头文件。

如果预编译期间存在重复的文本内容,在后续的编译过程中,一定存在效率的降低。那么,如何解决头文件的重复引入呢?

① 使用条件编译解决:

#ifndef __TEST_H__
#define __TEST_H__

#endif 

② 使用 #pragma:

#pragma once

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

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

相关文章

某度旋转验证码

案例地址:aHR0cHM6Ly96aXl1YW4uYmFpZHUuY29tL2xpbmtzdWJtaXQvdXJs 运行结果截图: 抓包分析, 整个流程如下 第一个包,提交参数是ak和时间戳(ak是定值) 返回的参数中,as和tk后面都会用到 点击提交,会弹出验证码,第二个包,请求参数的tk是第一个包返回的, ak同第一…

总算给女盆友讲明白了,如何使用stream流的filter()操作

一、引言 在上一篇文章中《这么简单&#xff0c;还不会使用java8 stream流的map()方法吗&#xff1f;》分享了使用stream的map()方法&#xff0c;不知道小伙伴还有印象吗&#xff0c;先来回顾下要点&#xff0c;map()方法是把一个流中的元素T转换为另外一个新流中的元素R&…

身边的那些信审人员都去哪了?

最近几天看到朋友圈很多信用卡审核中心的老同事&#xff08;老同学&#xff09;在秀到深圳9周年&#xff0c;在2013年的时候&#xff0c;大家都是一起通过校招来到了XX银行信用卡中心的信贷审批部&#xff0c;成为了信用卡人工审核员&#xff0c;那时候入职信贷审批部近百人&am…

这个算法不一般,控制拥塞有一手!

数字时代下&#xff0c;远程办公、线上协同成为刚需&#xff0c;直播带货等业务模式盛行&#xff0c;数据流量爆炸式增长&#xff0c;低时延、高流畅的网络传输诉求给数据中心的处理能力带来了极大挑战。RDMA作为一种新型网络传输技术&#xff0c;可大幅提升网络传输实效&#…

HTML期末学生大作业-节日网页作业html+css+javascript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

详细总结快慢指针的在链表中的常见题型

常见快慢指针题型1.找出链表中间结点2.找到倒数第K个结点3.判断环形链表4.找到环形链表的入口&#xff08;进阶&#xff09;5.相交链表1.找出链表中间结点 双指针进阶解法 1.定义两个指针&#xff0c;一个快指针&#xff0c;一个慢指针。 2.快指着一次走两步&#xff0c;慢指针…

基于冲突搜索的多机器人路径规划(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 随着自动化物流系统的发展,移动机器人作为运输系统的关键工具,各方面的技术得到了快速的发展。多移动机器人路径规划是机器人导…

什么是单臂路由技术?

使用vlan trunk可以在同一交换机上划分vlan&#xff0c;从而相同vlan的主机可以通信&#xff0c;不同vlan不能通信&#xff0c;如果连接不同vlan的主机想要通信&#xff0c;怎么办&#xff1f; 不同vlan是属于不同广播域的&#xff0c;配置的是不同的IP网段&#xff0c;针对不…

Notepad++官网地址及使用十六进制查看文件的详细教程

目录 一、概述 二、Notepad官网地址 三、Notepad查看十六进制教程  3.1 在线安装HexEditor插件  3.2 手动安装HexEditor插件 一、概述 Notepad是一个开源的源代码编辑器和记事本替代品&#xff0c;支持多种编程语言和自然语言。在MS Windows环境下运行&#xff0c;它的使用受…

在线数据图表制作-FineReport文本控件

1. 概述 1.1 版本 报表服务器版本 App 版本 功能变更 11.0 -- -- 11.0.2 11.0.54 App支持文本控件 NFC 读卡输入 1.2 应用场景 1.2.1 填报控件 填报报表中可以通过该控件输入文本信息&#xff0c;录入填报的数据&#xff0c;如下图所示&#xff1a; 1.2.2 参数控件 …

51单片机APP GSM短信老人跌倒定位温度异常报警检测GPS地图

实践制作DIY- GC0088-跌倒定位温度异常报警 一、功能说明&#xff1a; 基于STM32单片机设计-跌倒定位温度异常报警 功能介绍&#xff1a; STC15W4K48S4&#xff08;或者STM32F103CxT6&#xff09;系列最小系统板OLED显示器SIM800 GSM短信模块1个DS18B20温度测量模块蜂鸣器AD…

我的世界杯 - 诸神黄昏之战

话说长这么大还是头一回主动的去看世界杯比赛... 小时候只是我爷爷他们喜欢看 【CCTV-5】 各种球的比赛&#xff0c;而我我对此毫无兴趣可言&#xff0c;每天只要有时间就沉醉在属于我的 【少儿频道】&#xff0c;喜羊羊、葫芦娃、红猫蓝兔、小娜扎... 而这次可能是因为朋友圈的…

Spring【Bean的作用域与生命周期】

Spring【Bean的作用域与生命周期】&#x1f34e;一.Bean作用域问题&#x1f352;1.1 被修改的 Bean 案例&#x1f352;1.2 原因分析&#x1f34e;二.作⽤域定义&#x1f352;2.1Bean 的 6 种作⽤域&#x1f349; 2.1.1singleton(单例作⽤域)&#x1f349; 2.1.2prototype(原型作…

微服务守护神-Sentinel-概念

引言 书接上篇 微服务应对雪崩的容错方案 &#xff0c;大概知道微服务容错方案可以从隔离、超时、限流、熔断、降级这几方面入手。好了&#xff0c;理论了解了&#xff0c;那代码落地方案&#xff1f;这是后本篇的主角要登陆场&#xff1a;Sentinel&#xff0c;微服务的守护神…

Xylan-MAL|木聚糖-马来酰亚胺|木聚糖-聚乙二醇-马来酰亚胺|马来酰亚胺-PEG-木聚糖

Xylan-MAL|木聚糖-马来酰亚胺|木聚糖-聚乙二醇-马来酰亚胺|马来酰亚胺-PEG-木聚糖 中文名称&#xff1a;木聚糖-马来酰亚胺 英文名称&#xff1a;Xylan-MAL 别称&#xff1a;马来酰亚胺修饰木聚糖&#xff0c;马来酰亚胺-木聚糖 存储条件&#xff1a;-20C&#xff0c;避光&…

编程初学者应该先学C++、Java还是Python?

语言推荐&#xff1a; 第一大类语言包括Java、C、Python和C. 这类语言都是非常通用的语言,它们并不局限于特定的编程平台或用途。(无疑问&#xff0c;你应该熟悉这四种语言。) 第二大类语言包括Java、C#、PHP和Swift。 Java 和PHP是主要的Web开发语言。C# 是微软的编程语言&…

总结《你不知道的JavaScript》三卷小记

先讲一些废话 三本小黄书&#xff0c;工作日都抽出半小时来看看&#xff0c;三本书加来一共700多页的样子。我拖拖延延看了三个月&#xff0c;终于看完了。然后现在抽出时间随便写点&#xff0c;我只是挑一些自己想总结的&#xff0c;给自己一个名词概念&#xff0c;好了废话就…

FreeRTOS移植

FreeRTOS移植一、获取FreeRTOS源码1.1 官网下载1.2 源码路径二、移植2.1 工程内新建分支2.2 分支内添加文件FreeRTOS_COREFreeRTOS_PORTABLE添加完成2.3 添加 FreeRTOSConfig.h 文件2.4 添加 FreeRTOS 头文件路径三、举例3.1 包含头文件3.2 创建任务四、FreeRTOSConfig.h 附录一…

Redis——(7)redis作为mybatis缓存整合二级缓存的整合

1.作为mybits的缓存整合 1&#xff09;用户第一次访问的时候获取数据库的值&#xff0c;再次访问时直接从缓存中获取数据 2&#xff09;设置缓存过期时间 3)项目8080端口是对外端口&#xff08;向外部暴露的端口&#xff09;&#xff0c;区别于内部进程号,查内部端口用ps -ef|…

分布式环境下Spring Session Redis底层原理

1 自动装配 public class SessionAutoConfiguration {// SessionRepositoryFilterConfiguration用来配置核心的过滤器// 3 核心过滤器Configuration(proxyBeanMethods false)ConditionalOnWebApplication(type Type.SERVLET)Import({ ServletSessionRepositoryValidator.clas…