C语言进阶--程序的编译(预处理动作)+链接

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

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

第一种是翻译环境:将源代码转换为可执行的机器指令(0/1);

第二种是执行环境:用于实际执行代码。

2.详解编译+链接

2.1翻译环境

程序编译过程:

在这里插入图片描述

linux系统中的编译器gcc生成的目标文件:xxx.o

(不同的参数)

2.2编译本身也分为几个阶段

在这里插入图片描述
预编译/预处理:(文本操作)

  • 头文件的包含(#include)
  • #define定义符号的替换,删除定义的符号
  • 删除注释
  • ···

编译:把C语言代码转换成汇编代码(包括语法分析、词法分析、符号汇总、语义分析)

汇编:把汇编代码转换成二进制指令(机器指令);将编译时的符号汇总形成符号表

链接:合并段表;符号表的合并和重定位

gcc test.c -E -o test.ivim /usr/include/stdio.h    

在这里插入图片描述

gcc test.c -E -o test.i

在这里插入图片描述

gcc test.i -S  
//生成test.s

符号汇总:

在这里插入图片描述

gcc test.s -c
//生成test.o目标文件(二进制文件)

在这里插入图片描述
Linux下的可执行程序是elf格式。

2.3运行环境

程序执行的过程:

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

3.预处理详解

3.1预定义符号

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

这些预定义符号都是语言内置的。

#include <stdio.h>
int main()
{int i = 0;FILE* pf = fopen("log.txt", "w");if (pf == NULL){perror("fopen");return EXIT_FAILURE; //1//EXIT_SUCCESS; //0}for (i = 0; i < 10; i++){fprintf(pf, "file:%s line=%d date:%s time:%s i=%d", __FILE__, __LINE__, __DATE__, __TIME__, i);}fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

3.2#define

3.2.1#define定义标识符(不加分号)
#define name stuff
#include <stdio.h>#define MAX 1000
#define STR "hello bit"int main()
{int m = MAX;printf("%d\n", MAX); //1000printf("%s\n", STR); //hello bitreturn 0;
}

续行符
在这里插入图片描述

3.2.2#define定义宏

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

#define name(parament-list) stuff

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

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

#include <stdio.h>#define SQUARE(X) X*Xint main()
{int r = SQUARE(5); printf("%d\n", r); //25return 0;
}

上面写法有缺陷eg:

#include <stdio.h>#define SQUARE(X) X*Xint main()
{int r = SQUARE(5+1);  //5 + 1 * 5 + 1printf("%d\n", r); //11return 0;
}

修正:不吝啬括号

#include <stdio.h>#define SQUARE(X) ((X)*(X))int main()
{int r = SQUARE(5+1);  //((5 + 1) * (5 + 1))printf("%d\n", r); //36return 0;
}
3.2.3#define替换规则

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

  • 在调用宏时,首先对参数进行检查,看看是否包含任何由#defien定义的符号。如果是,它们首先被替换。
  • 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值所替换。
  • 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

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

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

#include <stdio.h>#define M 100
#define DOUBLE(X) ((X)+(X))int main()
{DOUBLE(M+2);  //(100+2)//((100+2)+(100+2))return 0;
}
3.2.4#和##

如何把参数插入到字符串中?
在这里插入图片描述

#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 = 10;PRINT(b);return 0;
}

在这里插入图片描述

#include <stdio.h>#define PRINT(N, FORMAT) printf("the value of "#N" is "FORMAT"\n", N)
int main()
{int a = 10;PRINT(a, %d); int f = 3.14f;PRINT(f, %lf);return 0;
}

在这里插入图片描述
##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。

#include <stdio.h>#define CAT(Class, Num) Class##Num
int main()
{int Class106 = 100;printf("%d\n", CAT(Class, 106)); //100//printf("%d\n", Class106);return 0;
}
3.2.5带副作用的宏参数

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

#include <stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 5; //6 7int b = 4; //5int m = MAX(a++, b++); //int m = ((a++)>(b++)?(a++):(b++));6      5   > 4   ?6printf("m=%d ", m); //6printf("a=%d b=%d\n", a, b); //7 5return 0;
}
3.2.6宏和函数对比

宏通常被应用于执行简单的运算。

eg:在2个数中找出较大的一个,为什么不用函数来完成这个任务呢?

原因:

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

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

函数调用(参数传参、栈帧创建)——>计算——>函数返回

宏的缺点:

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

2.宏是没法调试的。

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

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

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

#define MALLOC(num, type) (type*)malloc((num)*sizeof(type))int main()
{//malloc(40);//malloc(10, int); //errorint* p = (int*)MALLOC(10, int);//int* p = (int*)malloc((10)*sizeof(int));return 0;
}
属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏除外,程序的长度会大幅度增长函数代码只出现于一个地方。每次使用这个函数时,到调用那个地方
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写时多些括号函数参数只在函数调用时求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果函数参数只在传参的时候求值一次,结果更容易控制
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以适用于任何参数类型函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使它们执行的任务是相同的
调试宏是不方便调试的函数是可以逐语句/逐过程调试的
递归宏是不能递归的函数是可以递归的
3.2.7命名约定

一般来讲函数和宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的习惯是:把宏名全部大写;函数名不要全部大写

3.2.8#undef

这条指令用来移除一个宏定义。

#undef name
#define M 100#include <stdio.h>int main()
{printf("%d\n", M); //100
#undef M    printf("%d\n", M); //errorreturn 0;
}

3.3命令行定义

linux下gcc编译器实现:

在这里插入图片描述
编译指令

gcc test.c -D SZ=10

3.4条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或放弃是很方便的。因为我们由条件编译指令。

在这里插入图片描述
常见的条件编译指令:

#if 常量表达式//···
#endif
#include <stdio.h>#if 0
int main()
{printf("hehe\n");return 0;
}
#endif

在这里插入图片描述
2.多个分支的条件编译

#if 常量表达式//···
#elif 常量表达式//···
#else 常量表达式//···
#endif
#define MAX 3#include <stdio.h>int main()
{
#if M<5printf("hehe\n");
#elif M==5 printf("haha\n");
#elseprintf("heihei\n");
#endifreturn 0;
}

预编译代码:

在这里插入图片描述
3.判断是否被定义

#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol
//#define MAX 100#include <stdio.h>int main()
{
#if !defined(MAX)printf("max\n");
#endifreturn 0;
}
#define MAX 100#include <stdio.h>int main()
{
#ifdef MAXprintf("max\n");
#endifreturn 0;
}
//#define MAX 100#include <stdio.h>int main()
{
#ifndef MAXprintf("max\n");
#endifreturn 0;
}

4.嵌套指令

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

3.5文件包含

防止头文件被多次重复的包含。

方案1:

#ifndef __TEST_H__
#define __TEST_H__
···
#endif    

在这里插入图片描述
方案2:

#pragma once

在这里插入图片描述

#include <stdio.h> //库文件包含
#include "test.h" //本地文件包含

<>和""的区别:查找的策略不同。

<>查找策略:直接去库目录下查找;

""查找策略:先去代码所在的路径下查找;如果找不到,再去库目录下查找。

Linux环境的标准头文件的路径:

/usr/include

VS环境的标准头文件的路径:(不同版本有差异)

C:\Program Files(x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

4.其他预处理指令

#error
#pragma
#line
···
不做介绍,自己了解,参考《C语言深度解剖》
#pragma pack()在结构体部分介绍。    

百度笔试题(offsetof宏的实现)

写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明。
在这里插入图片描述

#include <stdio.h>
#include <stddef.h>struct S
{char c1;int i;char c2;
};
int main()
{struct S s = {0};printf("%d\n", offsetof(struct S, c1)); //0printf("%d\n", offsetof(struct S, i)); //4printf("%d\n", offsetof(struct S, c2)); //8return 0;
}
#define OFFSETOF(type, m_name)  (size_t)&(((type*)0)->m_name)
#include <stdio.h>struct S
{char c1;int i;char c2;
};
int main()
{struct S s = {0};printf("%d\n", OFFSETOF(struct S, c1)); //0printf("%d\n", OFFSETOF(struct S, i)); //4printf("%d\n", OFFSETOF(struct S, c2)); //8return 0;
}

总结

今天就暂且更新至此吧,期待下周再会。如有错误还请不吝赐教。希望对您学习有所帮助,翻页前留下你的支持,以防下次失踪了嗷。

作者更新不易,免费关注别手软。

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

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

相关文章

微调大模型:什么时候该做,什么时候不该做?

目录 一、什么是“微调”&#xff1f;你真的需要它吗&#xff1f; 二、什么时候不该微调&#xff1f; &#x1f6ab; 不该微调的 5 个典型场景&#xff1a; 1. 通用问答、闲聊、常识类内容 2. 企业内部问答 / 文档助手 3. 想要通过微调“学会格式” 4. 没有大量高质量标…

微深节能 码头装卸船机定位与控制系统 格雷母线

微深节能码头装卸船机定位与控制系统&#xff1a;格雷母线技术赋能港口作业智能化升级 在现代化港口散货装卸作业中&#xff0c;装卸船机是连接船舶与陆域运输的核心枢纽设备。传统装卸船机依赖人工操作&#xff0c;存在定位偏差大、动态协同难、安全风险高等痛点。微深节能基于…

如何检查popover气泡组件样式?调试悬停元素CSS样式的解决方案

1. 问题 当我们要检查这种弹出层的CSS样式时&#xff0c;会发现特别棘手&#xff0c;因为鼠标移走就消失了。如果是display:none控制的&#xff0c;可能还能找到&#xff0c;如果是用js通过v-if控制的&#xff0c;就无法调试了。 2. 解决方案 使用 setTimeout debugger 就…

网络攻防技术一:绪论

文章目录 一、网络空间CyberSpace1、定义2、基本四要素 二、网络空间安全1、定义2、保护对象3、安全属性4、作用空间 三、网络攻击1、攻击分类2、攻击过程 四、网络防护1、定义2、安全模型3、安全服务5类4、特定安全机制8种5、普遍性安全机制5种 五、网络安全技术发展简史1、第…

彻底理解Spring三级缓存机制

文章目录 前言一、Spring解决循环依赖时&#xff0c;为什么要使用三级缓存&#xff1f; 前言 Spring解决循环依赖的手段&#xff0c;是通过三级缓存&#xff1a; singletonObjects&#xff1a;存放所有生命周期完整的单例对象。&#xff08;一级缓存&#xff09;earlySingleto…

【 SpringCloud | 微服务 网关 】

单体架构时我们只需要完成一次用户登录、身份校验&#xff0c;就可以在所有业务中获取到用户信息。而微服务拆分后&#xff0c;每个微服务都独立部署&#xff0c;这就存在一些问题&#xff1a; 每个微服务都需要编写登录校验、用户信息获取的功能吗&#xff1f; 当微服务之间调…

【前端面经】字节跳动一面

写在前面&#xff1a;面经只是记录博主遇到的题目。每题的答案在编写文档的时候已经有问过deepseek&#xff0c;它只是一种比较普世的答案&#xff0c;要学得深入还是靠自己 Q&#xff1a;三栏布局的实现方式&#xff08;圣杯模型&#xff09;如何实现 A&#xff1a; /* 整个 …

ST-GCN

1.bash 安装git 在目录下右键使用git bash打开 需要安装wgetbash download_model.sh&#xff0c;下载.sh文件 wget: command not found&#xff0c;Windows系统使用git命令 下载预训练权重_sh文件下载-CSDN博客 bash tools/get_models.sh 生成了三个.pt文件

计算机网络全维度解析:架构协议、关键设备、安全机制与新兴技术深度融合

计算机网络作为当今数字化社会的基石&#xff0c;其复杂性和应用广泛性远超想象。本文将从基础架构、协议体系、关键设备、安全机制到新兴技术&#xff0c;进行全方位、深层次的解析&#xff0c;并辅以实际应用场景和案例分析。 一、网络架构与分类的深度剖析 1.1 网络分类的立…

大语言模型的推理能力

2025年&#xff0c;各种会推理的AI模型如雨后春笋般涌现&#xff0c;比如ChatGPT o1/o3/o4、DeepSeek r1、Gemini 2 Flash Thinking、Claude 3.7 Sonnet (Extended Thinking)。 对于工程上一些问题比如复杂的自然语言转sql&#xff0c;我们可能忍受模型的得到正确答案需要更多…

黑马程序员C++核心编程笔记--3 函数高级

3.1 函数默认参数 本节内容之前已经整理过&#xff0c;详见22.函数的默认值 3.2 函数占位参数 C中函数的形参列表里可以有占位参数&#xff0c;用来做占位&#xff0c;调用函数时必须补填该位置 语法&#xff1a; 返回值类型 函数名 (数据类型) {} 在现阶段函数的占位参数…

数据仓库分层 4 层模型是什么?

企业每天都在产生和收集海量数据。然而&#xff0c;面对这些数据&#xff0c;许多企业却陷入了困境&#xff1a;如何高效管理、处理和分析这些数据&#xff1f;如何从数据中提取有价值的信息来支持业务决策&#xff1f;这些问题困扰着众多数据分析师和 IT 管理者。 在众多架构…

Java正则表达式完全指南

Java正则表达式完全指南 一、正则表达式基础概念1.1 什么是正则表达式1.2 Java中的正则表达式支持 二、正则表达式基本语法2.1 普通字符2.2 元字符2.3 预定义字符类 三、Java中正则表达式的基本用法3.1 编译正则表达式3.2 创建Matcher对象并执行匹配3.3 常用的Matcher方法 四、…

缓存击穿、缓存雪崩、缓存穿透以及数据库缓存双写不一致问题

在项目中&#xff0c;我们所需要的数据通常存储在数据库中&#xff0c;但是数据库的数据保存在硬盘上&#xff0c;硬盘的读写操作很慢&#xff0c;为了避免直接访问数据库&#xff0c;我们可以使用 Redis 作为缓存层&#xff0c;缓存通常存储在内存中&#xff0c;内存的读写速度…

可灵2.1 vs Veo 3:AI视频生成谁更胜一筹?

在Google发布Veo 3几天后,可灵显然感受到了压力,发布了即将推出的视频模型系列可灵 2.1的早期体验版。 据我了解,有三种不同的模式: 可灵 2.1 标准模式: 720p分辨率 仅支持图像转视频(生成更快,一致性更好) 5秒视频仍需20积分 可灵 2.1 专业模式: 1080p分辨率 仅在图…

解决Docker存储空间不足问题

虚拟机磁盘扩展实战&#xff1a;解决Docker存储空间不足问题 问题背景 在虚拟机中运行的Linux系统上&#xff0c;Docker服务因根分区空间不足而无法正常运行。初始状态如下&#xff1a; [rootlocalhost ~]# df -h / 文件系统 容量 已用 可用 已用% 挂载点…

Redis 中如何保证缓存与数据库的数据一致性?

在 Redis 中保证缓存与数据库的数据一致性&#xff0c;需结合业务场景选择以下策略&#xff1a; 核心策略总结 Cache Aside&#xff08;旁路缓存&#xff09;模式 读操作&#xff1a;先查缓存&#xff0c;未命中则查数据库并写入缓存。写操作&#xff1a;先更新数据库&#xf…

晶振频率稳定性:5G 基站与航天设备的核心竞争力

在当今科技飞速发展的时代&#xff0c;电子设备的性能和可靠性至关重要。晶振作为电子设备中的核心部件&#xff0c;为系统提供精确的时间和频率基准。晶振的频率稳定性直接影响着设备的整体性能&#xff0c;从日常生活中广泛使用的智能手机、智能穿戴设备&#xff0c;到对精度…

PDFGear——完全免费且功能强大的PDF处理软件

关键词 &#xff1a;PDFGear、免费、跨平台、多功能、OCR 概要 &#xff1a;PDFGear是一款完全免费且功能强大的PDF处理软件&#xff0c;支持Windows、macOS、iOS和Android等多平台使用。它集PDF阅读、编辑、格式转换、OCR识别及AI智能助手于一体&#xff0c;满足用户多样化文档…

【笔记】在 MSYS2(MINGW64)中正确安装 Rust

#工作记录 1. 环境信息 Windows系统: MSYS2 MINGW64当前时间: 2025年6月1日Rust 版本: rustc 1.87.0 (17067e9ac 2025-05-09) (Rev2, Built by MSYS2 project) 2. 安装步骤 步骤 1: 更新系统包数据库并升级已安装的包 首先&#xff0c;确保我们的 MSYS2 系统是最新状态。打…