被隐藏的过程——预处理

news2024/9/24 1:27:29

文章目录

    • 0. 前言
    • 1. 程序的翻译环境和执行环境
    • 2. 被隐藏的过程
      • 2.1 翻译环境
      • 2.2 编译
        • 3.2.1 预编译
        • 3.2.2 编译
        • 2.2.3 汇编
      • 2.3 链接
      • 2.4 运行环境
    • 3. 预处理
      • 3.1 预定义符号
      • 3.2 #define
        • 3.2.1 #define定义标识符
        • 3.2.2 #define定义宏
        • 3.2.3 #define替换规则
        • 3.2.4 #和##
        • 3.2.5 带副作用的宏参数
        • 3.2.6 宏和函数对比
        • 3.2.7 命名约定
      • 3.3 undef
      • 3.4 命令行定义
      • 3.5 条件编译
      • 3.6 文件包含
        • 3.6.1 头文件被包含的方式
        • 3.6.2 嵌套文件包含
    • 4. 结语

0. 前言

现在的我们写代码大多数用的集成开发环境(IDE),比如Visual StudioIdea等,这样的IDE一般都将编译链接的过程一步完成。一句简简单单的Hello World,在我们看来,这一步到位,小菜一碟。可是一句话说的好不是岁月静好,只是有人在替你负重前行。这里面一些复杂的过程,集成工具已经默默的处理掉了。可是当我们写的程序出了一些莫名其妙的错误,让我们头大且掉发,我们只能看到这些问题的表象,难以看清本质,这些问题的本质就是软件运行背后的机理支撑软件运行的各种平台和工具,如果能够了解这些机制,那么对待这些问题,就会有新的看法。

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

在ANSI C存在两个不同的环境:

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

我们知道,计算机只能执行二进制指令,但我们一般写的代码,都不是以二进制形式写,以C语言为例:

#include<stdio.h>
int main()
{
	printf("C 语言\n");
	return 0;
}

这段C语言代码,如果要执行,那么就需要翻译环境将其翻译为二进制指令。我们所使用的一些编译器,就充当着"翻译官"的角色。当然了将我们的源代码,转换成可执行程序,这个翻译的过程能细分为2个步骤编译链接
在这里插入图片描述

2. 被隐藏的过程

2.1 翻译环境

在这里插入图片描述

组成一个程序的每个源文件通过编译过程转换成目标代码。
每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且也可以搜索到我们自己写的函数,将需要的函数也链接到程序中。

2.2 编译

编译可细分为三个小的过程:
在这里插入图片描述

3.2.1 预编译

首先是源代码文件和相关的头文件,被编译成一个 .i 文件。对于C程序来说,它的源文件拓展名是 .c,头文件拓展名是 .h ,而预编译后的文件拓展名是 .i

预编译命令(-E表示只进行预编译):
$ gcc -E test.c -o test.i

在这里插入图片描述

预编译过程主要处理那些源代码文件中的以 **#**开始的预编译指令。主要处理规则如下:

  • 将所有的 #define 删除,并且展开所有的宏定义。
  • 处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置。
  • 删除所有的注释 /// ** /
  • 添加行号和文件标识,比如 #7 “test.h” 2,以便于编译时编译器产生调试用的行号信息及用于产生编译错误或警告时能产生行号
  • 报了所有的 #pragma 编译器指令,因为编译器要使用他们。

经过预编译后,.i 文件不包含任何宏定义,因为所有的宏定义已经被展开,并且包含的文件也已经被插入到 .i 。所以无法判断宏定义头文件是否包含正确,那么接下来就是通过查看编译后的文件进行判断。

3.2.2 编译

编译过程就是把预处理完的文件进行一系列的词法分析语法分析语义分析符号汇总及优化后产生相应的汇编代码文件,这是这个程序构建的核心部分。

编译命令:

$ gcc -S test.i

在这里插入图片描述

2.2.3 汇编

汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎对应一条机器指令。所以汇编器的汇编过程相当于编译器来讲比较简单,它没有复杂的语法,也不用做优化指令,只根据汇编指令和机器指令的对照表一一翻译就可以了。原来汇编才是一个血统纯正的“翻译官”啊,不带任何“感情色彩”。

汇编命令:

$ gcc -c test.s

在这里插入图片描述
汇编完成后输出目标文件、将汇编代码翻译成二进制代码(存放到目标文件中),同时形成符号表

2.3 链接

现在软件开发过程中,软件的规模往往都很大,动辄数百万行的代码,如果将这些代码全部都放在一个模块肯定无法想象。所以我们一般在写代码的时候,会分模块,这些模块之间相互依赖又相互独立。
那么链接就能将这些模块拼接起来,最后产生一个可执行的程序。

2.4 运行环境

程序执行的过程:

  1. 程序必须载入到内存中。在有操作系统的环境中,这个一般由操作系统来完成。在独立的环境中,程序的载入必须手工来安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始,接着便调用main函数。
  3. 开始执行程序代码。这时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存中的变量会一直保留。
  4. 终止程序。正常终止main函数,也可能意外终止,程序挂掉了。

3. 预处理

3.1 预定义符号

int main()
{
	printf("%s\n", __FILE__);//进行编译的源文件的路径
	printf("%d\n", __LINE__);//文件当前行号
	printf("%s\n", __DATE__);//文件被编译的日期
	printf("%s\n", __TIME__);//文件被编译的时间
	//VS2022不支持
	printf("%s\n", __STDC__);//如果编译器遵循ANSI C,其值为1,否则未定义
	return 0;
}

通过gcc编译器可以发现,这些确实是在预编译阶段,就完成了替换
在这里插入图片描述

3.2 #define

3.2.1 #define定义标识符

语法:
#define name stuff

代码示例:

#define MAX 100
#define reg register		//为register关键字创建一个简短的名字
#define do_forever for(;;)  //死循环
#define CASE break;case		//在写case语句的时候自动把break写上。

// 如果定义的stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
					__FILE__,__LINE__ ,       \
					__DATE__,__TIME__ ) 

那么,我们在定义 #define 的时候后面是否要加上 ; 呢?
通过前面的预编译知识,#define 的内容将会被替换,加上 ; 会造成不必要的麻烦。

比如下面的场景:

#define MAX 100;
int main()
{
	int m = 0;
	if (m >= 0)
		m = MAX;
	else
		m = -1;
	return 0;
}

我们通过gcc编译器可以看到在预编译阶段,100 后面的 ==;==也被添加上去了,导致else匹配不到if语句。
在这里插入图片描述

3.2.2 #define定义宏

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

宏的声明方式:

#define name( parament-list ) stuff

注意:

  • 其中的partment-list是一个逗号隔开的符号表,它可能出现在sturff中。
  • 参数列表的左括号必须与name紧邻
  • 如果两者之间有任何空白存在,参数列表就会解释为stuff的一部分。

代码示例:

#define SQUARE(X) X*X //求一个数的平方
int main()
{
	printf("%d\n", SQUARE(5));
	printf("%lf\n", SQUARE(5.0));
	return 0;
}

但是呢,这段代码还存在一定的风险,如果在宏里面输入的是 (5+1),那么就会被替换成 5 + 1 * 5 + 1,这样输出的值就和我们原本的意愿不符合。
这里,我们在宏定义上加括号,就能很好的解决问题。
代码示例:

#define SQUARE(X) (X)*(X) //求一个数的平方
int main()
{
	printf("%d\n", SQUARE(5+1));
	return 0;
}
//输出 36

那这样就真的避免了风险吗?当然,避免了刚才出的问题,但是又产生了新的问题。
代码示例:

#define DOUBLE(X) (X)+(X) //求一个数的平方
int main()
{
	printf("%d\n", 10*DOUBLE(5));
	return 0;
}
//输出 55

我们原意是 10 * (5 + 5),可是这里替换成了 10 * 5 + 5,又违背了我们的意愿。
这个问题的解决办法是在宏定义表达式两边加上一对括号就可以了。

#define DOUBLE(X) ((X)+(X)) //求一个数的平方
int main()
{
	printf("%d\n", 10 * DOUBLE(5));
	return 0;
}
//输出 100

小贴士:

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

3.2.3 #define替换规则

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

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

注意:
1.宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归。
2.当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索

3.2.4 #和##

我们先来看这段代码:

int main()
{
	printf("hello world\n");
	printf("hello " "world\n");
	return 0;
}

这两句printf输出的内容其实都是一样的hello world,那我们就能得出结论:字符串是有自动连接特点的
有这个结论后,我们就可以这样写代码:

#define PRINT(format,x) printf("the value of "#x" is "format"\n",x)
int main()
{
	int a = 10;
	PRINT("%d", a);
	float b = 1.5;
	PRINT("%f", b);
	return 0;
}

这里 # 的作用就是把一个宏参数变成对于的字符串

##的作用

##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
注:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

代码示例:

#define CAT(x,y) x##y
// RMB##100
// RMB100
int main()
{
	int RMB100 = 20;
	printf("%d", CAT(RMB, 100));
	return 0;
}

3.2.5 带副作用的宏参数

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

例如:

int main()
{
	int a = 1;
	int b = a + 1;//无副作用
	int c = ++a; //有副作用
	return 0;
}

同理,下面这段代码就能充分证明宏参数所引起的副作用

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	//printf("%d\n", MAX(2, 3));
	int a = 4;
	int b = 5;
	int m = MAX(a++, b++);
	//预处理之后
	//int m = ((a++)>(b++)?(a++):(b++));
	//            5    6           7
	printf("%d\n", m);
	printf("a = %d b = %d\n", a,b);//5 7
	return 0;
}

3.2.6 宏和函数对比

宏通常用于执行简单的运算(两数中求大值)。
那用函数求,和这个有什么区别呢?

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。
    所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于来比较的类型。
    宏是类型无关的

当然了,现在写代码时,大部分还是写函数,很少写宏。
宏的缺点:

  • 宏定义是插入代码中,除非宏比较短,否则将大幅度增大程序的长度。
  • 宏在预处理就替换了,无法调试发现问题。
  • 宏与类型无关,自然也就不够严谨(双刃剑)。
  • 宏会带来运算符优先级的问题,容易导致出错。

宏和函数的一个对比:

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

3.2.7 命名约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:

把宏名全部大写
函数名不要全部大写

3.3 undef

用于移除宏定义

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

3.4 命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)

#include <stdio.h>
int main()
{
    int array [SZ];
    int i = 0;
    for(i = 0; i< SZ; i ++)
   {
        array[i] = i;
   }
    for(i = 0; i< SZ; i ++)
   {
        printf("%d " ,array[i]);
   }
    printf("\n" );
    return 0;
}

这里用gcc以命令行的形式操作,可以观察到,SZ通过命令行定义,发生了替换。
在这里插入图片描述

3.5 条件编译

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

调试性的代码,删除可惜,保留碍事,那么我们可以选择性的编译

代码示例:

#define _DEBUG_ 1
int main()
{
#ifdef _DEBUG_
	printf("1\n");
#endif

#ifdef DEBUG //未定义,所以不会编译
	printf("0\n");
#endif
	return 0;
}

常见的条件编译指令:

1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif
2.多个分支的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

3.6 文件包含

在C语言写的大大大部分的代码中,我们都会用到 #include 这条指令,这条指令可以使另一个文件被编译。

3.6.1 头文件被包含的方式

本地文件被包含:

#include "filename"

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

库文件包含:

#include <filename.h>

那这样来说,是不是包含库文件,就直接用 ==" "==包含不久行了吗?
理论可行,但是不切实际。
在这里插入图片描述

3.6.2 嵌套文件包含

当项目十分庞大或者我们不小心多次包含同一个文件时:
在这里插入图片描述
这样会造成文件的内容重复,那么我们可以通过条件编译来解决这个问题。

#ifndef _TEST_H_
#define _TEST_H_
//头文件的内容
#endif   //_TEST_H_

这样写可能会有些麻烦,写成下面这种形式就简单很多:

//在VS2022中,创建本地头文件,编译器会自动加上
#pragma once

4. 结语

本篇文章参考《程序员的自我修养——链接、装载与库》的第一章内容,之后也会慢慢更新从书本中学到的知识。本周上课听老师讲,计算机的一些专业名称的含义。
比如:计算机科学与技术,科学是摆在技术前面的,扎实的理论基础,更利于我们技术的提升,所以在有一定技术基础的前提下,可尝试学习部分理论,这样会让我们的水平再往上升。

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

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

相关文章

API Gateway vs Load Balancer:选择适合你的网络流量管理组件

本文从对比了 API Gateway 和 Load Balancer 的功能区别&#xff0c;帮助读者更好地了解他们在系统架构中扮演的角色。 作者陈泵&#xff0c;API7.ai 技术工程师。 原文链接 由于互联网技术的发展&#xff0c;网络数据的请求数节节攀升&#xff0c;这使得服务器承受的压力越来…

vue-virtual-scroll-list虚拟列表

当DOM中渲染的列表数据过多时&#xff0c;页面会非常卡顿&#xff0c;非常占用浏览器内存。可以使用虚拟列表来解决这个问题&#xff0c;即使有成百上千条数据&#xff0c;页面DOM元素始终控制在指定数量。 一、参考文档 https://www.npmjs.com/package/vue-virtual-scroll-li…

Web前端学习:章三 -- JavaScript预热(三)

六九&#xff1a;函数的变量提升 函数的变量提升没有var高&#xff0c;var是最高的。 先提var&#xff0c;再提函数 解析&#xff1a; 1、4行打印之前没有定义变量&#xff0c;预解析触发变量提升 2、先提var&#xff0c;再提函数。所以先把var提升到最上面&#xff0c;然后提…

【蓝牙系列】蓝牙5.4到底更新了什么(2)

【蓝牙系列】蓝牙5.4到底更新了什么&#xff08;2&#xff09; 一、 背景 上一篇文章讲了蓝牙5.4的PAwR特征&#xff0c;非常适合应用在电子货架标签&#xff08;ESL&#xff09;领域&#xff0c; 但是实际应用场景中看&#xff0c;只有PAwR特性是不够的&#xff0c;如何保证广…

【latex】总结最近使用到的画图、表格及公式操作

前言 推荐使用overleaf写latex文章&#xff0c;内含很多会议/期刊的模板&#xff0c;可以直接套用。 https://www.overleaf.com下文都是在写论文过程中比较头疼的部分&#xff0c;有人建议我写完文章&#xff0c;最后再调整格式。但图片过大看起来实在是不适~ 插入图片 \beg…

5GHz 你得先认识DFS

想用Wi-Fi 5GHz&#xff1f;你得先认识DFS&#xff01; 添加链接描述 无线网络2.4 GHz的频段&#xff0c;因为频道过少、使用技术过多太过拥挤&#xff0c;频宽性能不佳早已不是新闻。在5 GHz的频段&#xff0c;频道数大幅超过2.4 GHz&#xff0c;但其中也有一大部份是DFS频道…

【MySQL高级篇】第04章_逻辑架构

第04章_逻辑架构 1. 逻辑架构剖析 1.1 服务器处理客户端请求 首先MySQL是典型的C/S架构&#xff0c;即Clinet/Server 架构&#xff0c;服务端程序使用的mysqld。 不论客户端进程和服务器进程是采用哪种方式进行通信&#xff0c;最后实现的效果是&#xff1a;客户端进程向服…

线程池的原理

1. 为什么要用线程池降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。提高响应速度。当任务到达时&#xff0c;任务可以不需要等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源&#xff0c;如果无限制的创建&#xff0c;不仅会消耗系统…

Javaweb之会话跟踪技术

1.会话跟踪技术的概述 会话跟踪技术就是处理一次会话中多次请求间数据共享问题 会话:用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&#xff0c;直到有一方断开连接&#xff0c;会话结束。在一次会话中可以包含多次请求和响应。 从浏览器发出请求到服务…

vue 行内样式 px 单位 转换为 vw

vue2 安装 插件 npm i style-vw-loader --save vue.config.js 文件配置 module.exports {chainWebpack: (config) > {config.module.rule(vue).test(/\.vue$/).use(style-vw-loader).loader(style-vw-loader).options({unitToConvert: "px",//需要转换的单位vi…

讲透前端工程开发工具发展与使用

前端⼯程化的发展及⼯具详解 什么是⼯程化&#xff1f;什么是前端⼯程化&#xff1f; 随着发展的逐步发展&#xff0c;作为⼯程师除了需要关注需要写的⻚⾯&#xff0c;样式和逻辑之外&#xff0c;还需要⾯对⽇益复杂的系统性问题&#xff0c;⽐如模块化⽂件的组织、ES6 JS ⽂…

为了满足国内市场快速发展的业务需求,理想汽车选择亚马逊云科技

理想汽车是一家用户驱动的汽车科技企业&#xff0c;坚持核心技术全栈自研&#xff0c;理想ONE是其首个单车突破20万辆的智能电动车产品。借助亚马逊云科技领先技术和多项托管服务&#xff0c;理想汽车迅速构建起安全稳定、技术架构先进的车联网云计算处理平台&#xff0c;服务于…

R语言基础(二):常用函数

接前文&#xff1a; R语言基础(一)&#xff1a;注释、变量 3.常用函数 函数就是一些已经编写好的功能&#xff0c;我们拿过来直接使用就可以了。 3.1 查看变量ls() 也许你清空了控制台&#xff0c;看不到之前的变量。但是它一直存在于系统中。 我们可以使用ls()函数查看已经定…

事件响应必备:DNS攻击与防御矩阵

攻击者采用了哪些DNS攻击技术&#xff0c;哪些组织可以帮助事件响应团队检测、缓解和预防这些技术&#xff1f;FIRST近日发布的DNS攻击与防御矩阵提供了答案。 DNS作为互联网基础架构的一项核心服务&#xff0c;安全问题严峻&#xff0c;各种攻击层出不穷。F5发布的数据显示&a…

项目管理手册

1 概述 1.1 项目管理体系 1.1.1 体系基础 项目管理体系是建立在公司 ISO 9000 质量管理体系基础上&#xff0c;结合 PMI 项目管理框架与 CMMI 能力成熟度模型&#xff0c;针对项目实施状态&#xff0c;对一些重点环节进行细化&#xff0c;加强重点环节的监控&#xff0c;明确…

Python导入模块的3种方式(超级详细)

很多初学者经常遇到这样的问题&#xff0c;即自定义 Python 模板后&#xff0c;在其它文件中用 import&#xff08;或 from...import&#xff09; 语句引入该文件时&#xff0c;Python 解释器同时如下错误&#xff1a; ModuleNotFoundError: No module named 模块名 意思是 Pyt…

VS2022安装EasyX 及 EasyX图形库安装和使用(附C++各图形编程项目示例源码)

文章目录一、EasyX的安装二、C_EasyX 项目1. 樱花2. 雪花3. 小熊4. 跳动爱心5. 橘子钟表6. 红玫瑰7. 奥特曼三、更多项目资源EasyX提取链接 网盘链接&#xff1a;https://pan.baidu.com/s/1gPtRVZub_008jwcK11Bb-g?pwd9ol9 提取码&#xff1a;9ol9 什么是EasyX&#xff1f; E…

GTC08L可替代启攀微八通道CP2528、CP2682

由工采网代理提供的八通道电容式触摸传感芯片—GTC08L可完美替代启攀微CP2528、CP2682、CP2688等多款八按键触摸芯片。 芯片介绍&#xff1a; GTC08L采用SOP-16L(9.90x3.90x1.40,e1.27) 封装&#xff1b;电源电压范围&#xff1a;2.7V&#xff5e;5.5V具有各种智能传感功能&…

使用JProfiler分析java oom dump文件

1、安装JProfiler&#xff08;本文使用JProfiler11&#xff09; 链接&#xff1a;https://pan.baidu.com/s/1VBHLIo8hIVGeeLjaBVjcIg 提取码&#xff1a;q5wl 在JProfiler的堆遍历器(Heap Walker)中&#xff0c;你可以对堆的状况进行快照并且可以通过选择步骤下寻找感兴趣的对…

一阶LADRC笔记代码实现

这两天看了一下LADRC的。学习深度不够&#xff0c;从理论和原理&#xff0c;没法评论什么。从个人感受上&#xff0c;它会从另一种角度去解释一些的控制的东西。从工程使用上&#xff0c;还是很有参考价值的&#xff0c;参数意义比较明确&#xff0c;整定参数比较容易。 参考&…