gcc/linux下的c++异常实现

news2024/9/24 9:23:22

概述

本文不一定具有很好的说教性,仅作为自我学习的笔记。不妨可参阅国外大神博文C++ exceptions under the hood链接中包含了大量的例子。

偶有在对ELF做分析的时候看到如下图一些注释,部分关键字看不懂,比如什么FDE, unwind , __gxx_personality_v0,__cxa_end_catch ,__cxa_start_catch等是什么?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于笔者在Android开发领域,因此偏向ARM下做实现,部分源码有时候会参阅AOSP或者linux版本。

操作系统环境如下:
在这里插入图片描述

站在编译器角度学习

Itanium曾提出了相关异常规范,在很多GUN编译器都遵循相关ABI规范,具体可参阅:
Itanium C++ ABI: Exception Handling ($Revision: 1.22 $)

其中有两个术语要进行同步:

landing pad:异常处理中会进行两个操作,其中一个是运行资源回收代码,另一个是异常捕获处理代码。这两个代码块我们称为landing pad

personality routines:异常处理的核心函数,用于判断当前函数是否可以处理异常或者进行栈回溯操作(unwind)在我们c++中这个函数名字为 __gxx_personality_v0

个人理解如下:

//mythrow.cpp
#include <stdio.h>

class MyClass
{
public:
    ~MyClass(){}
};
class MyFakeExeception{};
class MyFakeExeception2{};

void testThrow(int param)
{
    if (param == 1)
    {
        throw MyFakeExeception();
    }
    else if (param == 2)
    {
        throw MyFakeExeception2();
    }
}

void myCleanFun(void *p)
{
    printf("myCleanFun invoke \r\n");
}
void testCatch(int param)
{
    printf("testCatch start \r\n");

    try
    {
        MyClass __attribute__((cleanup(myCleanFun))) mypro;
        //...
        // do thing with mypro ;
        testThrow(param);
    }
    catch (const MyFakeExeception &e)
    {
        printf("testCatch catch MyFakeExeception\r\n");
    }
    catch (const MyFakeExeception2 &e)
    {
        printf("testCatch catch MyFakeExeception2 \r\n");
    }
    catch (...)
    {
        printf("testCatch catch ...\r\n");
    }
    printf("testCatch done \r\n");
}

我们以testCatch函数举例。在调用testThrow函数会触发异常那么调用到personality routines函数此时假设判断函数可以捕获异常,那么首先我们需要释放掉try函数块的资源,在本例中需要调用MyClass析构函数和myCleanFun函数,这部分代码便是一个landing pad,在处理完成后需要调用catch块也是一个landing pad

那么编译器是如何完成异常的识别和栈回溯(unwind)的呢?

我们在创建一个mymain.cpp文件使其可以完整运行。

//mymain.cpp
#include <stdio.h>
extern void testCatch(int param);
int main(int argc, char **args)
{
    testCatch(argc);
    return 0;
}
//编译成中间文件
g++ -O0 -ggdb -c  mythrow.cpp -o  mythrow.o
//编译成中间文件
g++ -O0 -ggdb -c  mymain.cpp -o  mymain.o
//链接成可执行文件
g++ -O0 -ggdb  mythrow.o mymain.o  -o  app

当我们运行app输出:

testCatch start 
myCleanFun invoke 
testCatch catch MyFakeExeception
testCatch done

当我们输出输出app文件的节头 其中有几个非常有趣的节区:

readelf -S -W app

输出:
在这里插入图片描述
.eh_frame_hdr.eh_frame 用于栈回溯使用。
.gcc_except_table 也被称为LSDA(Language Specific Data Area)节,这个节专门用于存储跟语言相关的特性,这里用于存储函数可以捕获哪些异常已经try catch的信息(可以理解为java字节码中异常表)。
有了这几个节我们便可以在异常时候进行栈回溯以及landing pad寻找。

栈回溯是什么

我们知道异常发生时候需要进行分支操作,即跳转别的地方处理异常,处理完成后跳转回正常业务流程代码。那么这个流程就需要进行上下文的恢复和存储等(注:栈回溯在正常的函数调用也是需要的而非局限于异常)。

使用如下命令可查看.eh_frame_hdr.eh_frame 存储的内容

readelf -wF app

在这里插入图片描述
.eh_frame_hdr.eh_frame里面就包含了被称为CIEFDE东西,可以辅助我们调试或者栈回溯的时候进行上下文恢复。
如果你有兴趣了解更多可以参阅(参考文献是x86架构不过不影响理解):
linux 栈回溯(x86_64 )

另一个问题编译器是如何构造这个节的呢?
这是通过编译器的CFI directives,指令列表可参阅CFI-directives

我们使用下列命令仅进行汇编

g++ -O0 -ggdb -S mythrow.cpp

在这里插入图片描述

调用throw会发生什么

我们继续如法炮制查看对应的汇编函数

g++ -O0 -ggdb -S mythrow.cpp

我们查看testThrow函数对应的汇编

_Z9testThrowi:
.LFB3:
	.loc 1 19 1
	.cfi_startproc
	stp	x29, x30, [sp, -32]!
	.cfi_def_cfa_offset 32
	.cfi_offset 29, -32
	.cfi_offset 30, -24
	mov	x29, sp
	str	w0, [sp, 28]
	.loc 1 20 5
	ldr	w0, [sp, 28]
	cmp	w0, 1
	bne	.L3
	.loc 1 22 32
	mov	x0, 1
	bl	__cxa_allocate_exception
	mov	x3, x0
	mov	x2, 0
	adrp	x0, _ZTI16MyFakeExeception
	add	x1, x0, :lo12:_ZTI16MyFakeExeception
	mov	x0, x3
	bl	__cxa_throw
.L3:
	.loc 1 24 10
	ldr	w0, [sp, 28]
	cmp	w0, 2
	bne	.L5
	.loc 1 26 33
	mov	x0, 1
	bl	__cxa_allocate_exception
	mov	x3, x0
	mov	x2, 0
	adrp	x0, _ZTI17MyFakeExeception2
	add	x1, x0, :lo12:_ZTI17MyFakeExeception2
	mov	x0, x3
	bl	__cxa_throw
.L5:
	.loc 1 28 1
	nop
	ldp	x29, x30, [sp], 32
	.cfi_restore 30
	.cfi_restore 29
	.cfi_def_cfa_offset 0
	ret
	.cfi_endproc

注:testThrow函数经过c++编译器名称粉碎(name mangling)之后变为_Z9testThrowi
上面的汇编的代码可以看到我们可以知道对于c++的每个throw关键字,会生成一对__cxa_allocate_exception__cxa_throw函数的调用。
__cxa_allocate_exception函数用于分配异常对象创建,而__cxa_throw就是完成异常抛出给personality routines
我们在IDA PRO查看这两个函数发现都在plt节中
在这里插入图片描述
在这里插入图片描述
所以这两个函数在SO中被动态加载,使用ldd查看所依赖的so。

 ldd app

在这里插入图片描述
这里函数具体位于libstdc++.so.6

 nm -D /lib/aarch64-linux-gnu/libstdc++.so.6 | grep -i "__cxa_allocate_exception"
 nm -D /lib/aarch64-linux-gnu/libstdc++.so.6 | grep -i "__cxa_throw"

在这里插入图片描述
在这里插入图片描述
当然我们最重要的 __gxx_personality_v0也在这个库里面。
在这里插入图片描述
我查看linux源码的时候可以对应如下的源码

void
__cxa_throw(void *thrown_object, std::type_info *tinfo, void (_LIBCXXABI_DTOR_FUNC *dest)(void *)) {
 	//...
    _Unwind_RaiseException(&exception_header->unwindHeader);
 	//...
 	//如果没找到可以处理异常landing pad就结束程序
 	failed_throw(exception_header);
}

我们重点下_Unwind_RaiseException做了什么

_Unwind_Reason_Code LIBGCC2_UNWIND_ATTRIBUTE
_Unwind_RaiseException(struct _Unwind_Exception *exc)
{
  struct _Unwind_Context this_context, cur_context;
  _Unwind_Reason_Code code;
  unsigned long frames;

  /* Set up this_context to describe the current stack frame.  */
  uw_init_context (&this_context);
  cur_context = this_context;

  /* Phase 1: Search.  Unwind the stack, calling the personality routine
     with the _UA_SEARCH_PHASE flag set.  Do not modify the stack yet.  */
  while (1)
    {
      _Unwind_FrameState fs;

      /* Set up fs to describe the FDE for the caller of cur_context.  The
	 first time through the loop, that means __cxa_throw.  */
      code = uw_frame_state_for (&cur_context, &fs);

      if (code == _URC_END_OF_STACK)
	/* Hit end of stack with no handler found.  */
	return _URC_END_OF_STACK;

      if (code != _URC_NO_REASON)
	/* Some error encountered.  Usually the unwinder doesn't
	   diagnose these and merely crashes.  */
	return _URC_FATAL_PHASE1_ERROR;

      /* Unwind successful.  Run the personality routine, if any.  */
      if (fs.personality)
	{
	  code = (*fs.personality) (1, _UA_SEARCH_PHASE, exc->exception_class,
				    exc, &cur_context);
	  if (code == _URC_HANDLER_FOUND)
	    break;
	  else if (code != _URC_CONTINUE_UNWIND)
	    return _URC_FATAL_PHASE1_ERROR;
	}

      /* Update cur_context to describe the same frame as fs.  */
      uw_update_context (&cur_context, &fs);
    }

  /* Indicate to _Unwind_Resume and associated subroutines that this
     is not a forced unwind.  Further, note where we found a handler.  */
  exc->private_1 = 0;
  exc->private_2 = uw_identify_context (&cur_context);

  cur_context = this_context;
  code = _Unwind_RaiseException_Phase2 (exc, &cur_context, &frames);
  if (code != _URC_INSTALL_CONTEXT)
    return code;

  uw_install_context (&this_context, &cur_context, frames);
}

上面的代码将异常处理分为两个阶段:搜索阶段和回溯处理阶段。
在搜索阶段借助.eh_frame_hdr,.eh_frame以及.gcc_except_table 判断调用栈是否存在一个处理异常的调用__gxx_personality_v0判断。如果没有就返回到上一级函数__cxa_throw结束程序。
如果找到调用_Unwind_RaiseException_Phase2设置好上下文进行栈回溯的流程直到到异常处理块(期间会不断释放栈资源)。

我们使用gdb做一个有趣的实验查看对应的调用栈,

//给个性函数设置断点
(gdb) break  __gxx_personality_v0
(gdb) r
(gdb) bt

输出如下
在这里插入图片描述
可见和我们猜测差不多。

调用try catch会发生什么

如法炮制查看testCatch函数汇编

g++ -O0 -ggdb -S mythrow.cpp

对于没有耐心的同学可以跳过这段代码看结论

_Z9testCatchi:
.LFB5:
	.loc 1 35 1
	.cfi_startproc
	.cfi_personality 0x9b,DW.ref.__gxx_personality_v0
	.cfi_lsda 0x1b,.LLSDA5
	stp	x29, x30, [sp, -80]!
	.cfi_def_cfa_offset 80
	.cfi_offset 29, -80
	.cfi_offset 30, -72
	mov	x29, sp
	stp	x19, x20, [sp, 16]
	.cfi_offset 19, -64
	.cfi_offset 20, -56
	str	w0, [sp, 44]
	.loc 1 35 1
	adrp	x0, :got:__stack_chk_guard
	ldr	x0, [x0, #:got_lo12:__stack_chk_guard]
	ldr	x1, [x0]
	str	x1, [sp, 72]
	mov	x1, 0
	.loc 1 36 11
	adrp	x0, .LC1
	add	x0, x0, :lo12:.LC1
.LEHB0:
	bl	puts
.LEHE0:
.LBB2:
	.loc 1 43 18
	ldr	w0, [sp, 44]
.LEHB1:
	bl	_Z9testThrowi
.LEHE1:
	.loc 1 40 54
	add	x0, sp, 48
.LEHB2:
	bl	_Z10myCleanFunPv
.LEHE2:
	.loc 1 40 54 is_stmt 0 discriminator 1
	add	x0, sp, 48
	bl	_ZN7MyClassD1Ev
.L13:
.LBE2:
	.loc 1 57 11 is_stmt 1
	adrp	x0, .LC2
	add	x0, x0, :lo12:.LC2
.LEHB3:
	bl	puts
.LEHE3:
	.loc 1 58 1
	nop
	adrp	x0, :got:__stack_chk_guard
	ldr	x0, [x0, #:got_lo12:__stack_chk_guard]
	ldr	x2, [sp, 72]
	ldr	x1, [x0]
	subs	x2, x2, x1
	mov	x1, 0
	beq	.L17
	b	.L23
.L18:
.LBB3:
	.loc 1 40 54
	mov	x20, x0
	mov	x19, x1
	add	x0, sp, 48
	bl	_Z10myCleanFunPv
	add	x0, sp, 48
	bl	_ZN7MyClassD1Ev
	mov	x0, x20
	mov	x1, x19
	b	.L9
.L19:
.L9:
.LBE3:
	.loc 1 45 5
	cmp	x1, 1
	beq	.L10
	cmp	x1, 2
	beq	.L11
	b	.L24
.L10:
.LBB4:
	.loc 1 45 36 discriminator 1
	bl	__cxa_begin_catch
	str	x0, [sp, 64]
	.loc 1 47 15 discriminator 1
	adrp	x0, .LC3
	add	x0, x0, :lo12:.LC3
.LEHB4:
	bl	puts
.LEHE4:
	.loc 1 48 5
	bl	__cxa_end_catch
	b	.L13
.L11:
.LBE4:
.LBB5:
	.loc 1 49 37
	bl	__cxa_begin_catch
	str	x0, [sp, 56]
	.loc 1 51 15
	adrp	x0, .LC4
	add	x0, x0, :lo12:.LC4
.LEHB5:
	bl	puts
.LEHE5:
	.loc 1 52 5
	bl	__cxa_end_catch
	b	.L13
.L24:
.LBE5:
	.loc 1 53 12
	bl	__cxa_begin_catch
	.loc 1 55 15
	adrp	x0, .LC5
	add	x0, x0, :lo12:.LC5
.LEHB6:
	bl	puts
.LEHE6:
.LEHB7:
	.loc 1 56 5
	bl	__cxa_end_catch
	b	.L13
.L20:
.LBB6:
	.loc 1 48 5
	mov	x19, x0
	bl	__cxa_end_catch
	mov	x0, x19
	bl	_Unwind_Resume
.L21:
.LBE6:
.LBB7:
	.loc 1 52 5
	mov	x19, x0
	bl	__cxa_end_catch
	mov	x0, x19
	bl	_Unwind_Resume
.LEHE7:
.L22:
.LBE7:
	.loc 1 56 5
	mov	x19, x0
	bl	__cxa_end_catch
	mov	x0, x19
.LEHB8:
	bl	_Unwind_Resume
.LEHE8:
.L23:
	.loc 1 58 1
	bl	__stack_chk_fail
.L17:
	ldp	x19, x20, [sp, 16]
	ldp	x29, x30, [sp], 80
	.cfi_restore 30
	.cfi_restore 29
	.cfi_restore 19
	.cfi_restore 20
	.cfi_def_cfa_offset 0
	ret
	.cfi_endproc

每个catch会生成如下三个函数的调用:
__cxa_begin_catch
__cxa_end_catch
_Unwind_Resume

__cxa_begin_catch: 主要将异常对象放到栈上,方便异常处理代码使用。
__cxa_end_catch:异常代码运行后,进行异常资源清理操作
_Unwind_Resume:异常处理完成需要回到正常业务逻辑代码上。

我们查看Android下__cxa_begin_catch对应的源码

extern "C" void* __cxa_begin_catch(void* exc) {
    _Unwind_Exception *exception = static_cast<_Unwind_Exception*>(exc);
    __cxa_exception* header = reinterpret_cast<__cxa_exception*>(exception+1)-1;
    __cxa_eh_globals* globals = __cxa_get_globals();
    if (!isOurCxxException(exception->exception_class)) {
      if (globals->caughtExceptions) {
        fatalError("Can't handle non-C++ exception!");
      }
    }
    // Check rethrow flag
    header->handlerCount = (header->handlerCount < 0) ?
      (-header->handlerCount+1) : (header->handlerCount+1);
    if (header != globals->caughtExceptions) {
      header->nextException = globals->caughtExceptions;
      globals->caughtExceptions = header;
    }
    globals->uncaughtExceptions -= 1;
    //返回异信息。然后会被放入栈上
    return header->adjustedPtr;
  }

相关源码可参阅cxxabi.cc

gcc_except_table相关

gcc_except_table也是在汇编文件生成指定数据

g++ -O0 -ggdb -S mythrow.cpp

当你阅读mythrow.s文件的时候会看到一个定义节directives指令

.section	.gcc_except_table,"a",@progbits
	.align	2
.LLSDA5:
	.byte	0xff
	.byte	0x9b
	.uleb128 .LLSDATT5-.LLSDATTD5
.LLSDATTD5:
	.byte	0x1
	.uleb128 .LLSDACSE5-.LLSDACSB5
.LLSDACSB5:
	.uleb128 .LEHB0-.LFB5
	.uleb128 .LEHE0-.LEHB0
	.uleb128 0
	.uleb128 0
	....

具体解析可以参阅其他文献。

使用nothrow会怎么样(或者noexcept)?
我们给抛出异常的函数添加不会抛出异常
在这里插入图片描述
未声明前相关区大小
在这里插入图片描述
声明后节区大小
在这里插入图片描述
运行后

testCatch start 
terminate called after throwing an instance of 'MyFakeExeception'
Aborted (core dumped)

声明不会抛出异常的函数会减少生成相关文件大小使其更紧凑,但需要注意代码是否会违背规则的情况存在。

参考文献:
C++ exceptions under the hood
c++ 异常处理(上)
c++ 异常处理(下)
Itanium C++ ABI: Exception Handling ($Revision: 1.22 $)
What is the “C++ ABI Specification” referred to in GCC’s manual?
gcc_except_table
CPP 异常处理机制初探
Unwind 栈回溯详解
Chapter 8. Exception Frames
Serial- / Socket IO and GCC nothrow attribute
Itanium C++ ABI
C++对象模型之RTTI的实现原理
Android cxxabi

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

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

相关文章

【技巧分享】如何获取子窗体选择了多少记录数?一招搞定!

Hi,大家好久不见。 我这个更新速度是不是太慢了呀&#xff0c;因为&#xff0c;最近又又又在忙&#xff0c;请大家谅解啦。 现在更新文章、视频都要花好久去考虑&#xff0c;好不容易有个灵感了&#xff0c;一搜索&#xff0c;结果发现之前都已经分享过了&#xff08;委屈脸&…

Nginx详解 第三部分:Nginx高级配置(附配置实例)

Part 3 一、网页的状态页二、Nginx第三方模块2.1 echo 模块 三、变量3.1 内置变量3.1.1 常用内置变量3.1.2 举个例子 3.2 自定义变量 四、自定义访问日志 (优化)4.1 自定义访问日志的格式4.2 自定义json 格式日志 五、Nginx压缩功能&#xff08;重要&#xff09;六、HTTPS 功能…

SMC_Interpolator2Dir反向插补运动

附加函数是&#xff1a; SMC_Interpolator2Dir_SlowTask 函数的位置&#xff1a; 输入&#xff1a; 运行 bExecute 【BOOL】 路径包 poqDataIn 指向SMC_OUTQUEUE的指针 停止 bSlow_Stop 停止BOOL 急停 bEmergency_Stop 紧急停止BOOL 单…

2023.8 - java - 多态

多态是同一个行为具有多个不同表现形式或形态的能力。 多态就是同一个接口&#xff0c;使用不同的实例而执行不同操作&#xff0c; 多态的优点 1. 可替换性2 可扩充性3. 接口性、灵活性、简化性4. 消除类型之间的耦合关系 多态存在的三个必要条件 继承重写父类引用指向子类…

Java“牵手”天猫商品历史价格信息API接口数据,天猫API接口申请指南

天猫平台商品历史价格接口是开放平台提供的一种API接口&#xff0c;通过调用API接口&#xff0c;开发者可以获取天猫商品的标题、价格、库存、月销量、总销量、库存、详情描述、图片、最低价、当前价格、价格信息等详细信息 。 获取商品历史价格接口API是一种用于获取电商平台…

如何运行imbalanced-streams-master工程?

1 下载和安装JDK和Eclipse 这个步骤网上的教程特别多&#xff0c;在此就不特别叙述了。 2 将imbalanced-streams-master工程解压并引入 2.1 解压 特别要注意的是&#xff1a;解压的文件夹和workspace一定不要在同一个目录。 解压后的目录结构如下&#xff1a; 2.2 import…

图片换脸-->>视频换脸-->>直播换脸

资源网站&#xff1a;https://tianfeng.space/ 个人娱乐&#xff0c;切勿作恶 下载 ​ 网盘&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1DHMY1mCXpT0OtpmlvIoMKA 提取码&#xff1a;nf57 使用 下载解压后&#xff0c;打开 第一个就是你要替换的人脸&#xff0c;…

学习率调整策略

学习率是可以控制更新的步伐的。 我们在训练模型的时候&#xff0c;一般开始的时候学习率会比较大&#xff0c;这样可以以一个比较快的速度到达最优点的附近&#xff0c;然后再把学习率降下来&#xff0c; 缓慢的去收敛到最优值。学习率前期要大&#xff0c;后期要小 在学习学…

生成对抗网络(GAN):在图像生成和修复中的应用

文章目录 什么是生成对抗网络&#xff08;GAN&#xff09;&#xff1f;GAN在图像生成中的应用图像生成风格迁移 GAN在图像修复中的应用图像修复 拓展应用领域总结 &#x1f389;欢迎来到AIGC人工智能专栏~生成对抗网络&#xff08;GAN&#xff09;&#xff1a;在图像生成和修复…

PCI/PCIE总线的宏观理解

1、pcie总线协议实现的效果 (1)像访问内存一样去访问外设&#xff1b; (2)当建立好CPU地址空间到PCI/PCIE地址空间的映射关系后&#xff0c;程序访问CPU地址空间就可以达到访问PCI/PCIE地址空间的效果&#xff1b; 2、芯片地址空间 (1)32位的CPU寻址范围是4G&#xff0c;64位的…

【算法训练-链表】反转链表、区间反转链表、K个一组反转链表

从今天开始进行高频算法的训练&#xff0c;一方面训练自己的逻辑思维&#xff0c;一方面保持自己的竞争力。训练过程有这么两个基准原则&#xff1a; 首先训练题的来源呢有三个&#xff0c;首选的是三个都出现过的高频题&#xff0c;以&#xff1a;牛客101为基准分类&#xff…

渗透测试工具ZAP入门教程(2)-HUD教程

平视显示器 HUD是一种全新的与ZAP进行交互的方式。 它将安全信息叠加到你正在测试的应用程序上&#xff0c;并允许你访问关键的ZAP功能。 对于刚接触安全的人来说&#xff0c;它更易于理解&#xff0c;但同时也允许经验丰富的渗透测试人员将重点放在他们正在测试的应用程序上。…

计算机网络(速率、宽带、吞吐量、时延、发送时延)

速率&#xff1a; 最重要的一个性能指标。 指的是数据的传送速率&#xff0c;也称为数据率 (data rate) 或比特率 (bit rate)。 单位&#xff1a;bit/s&#xff0c;或 kbit/s、Mbit/s、 Gbit/s 等。 例如 4 1010 bit/s 的数据率就记为 40 Gbit/s。 速率往往是指额定速率或…

图床项目进度(二)——动态酷炫首页

前言&#xff1a; 前面的文章我不是说我简单copy了站友的一个登录页吗&#xff0c;我感觉还是太单调了&#xff0c;想加一个好看的背景。 但是我前端的水平哪里够啊&#xff0c;于是在网上找了找制作动态背景的插件。 效果如下图。 如何使用 这个插件是particles.js 安装…

vue2 自定义指令,插槽

一、学习目标 1.自定义指令 基本语法&#xff08;全局、局部注册&#xff09;指令的值v-loading的指令封装 2.插槽 默认插槽具名插槽作用域插槽 二、自定义指令 1.指令介绍 内置指令&#xff1a;v-html、v-if、v-bind、v-on… 这都是Vue给咱们内置的一些指令&#xff0c;…

代码随想录第31天|认识贪心算法,455.分发饼干,376. 摆动序列,53.最大子数组和

贪心的介绍 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 例如&#xff0c;有一堆钞票&#xff0c;你可以拿走十张&#xff0c;如果想达到最大的金额&#xff0c;你要怎么拿&#xff1f; 指定每次拿最大的&#xff0c;最终结果就是拿走最大数额的钱。…

Ansible 生成硬件报告

生成硬件报告 创建一个名为 /home/greg/ansible/hwreport.yml 的 playbook &#xff0c;它将在所有受管节点上生成含有以下信息的输出文件 /root/hwreport.txt &#xff1a; 清单主机名称 以 MB 表示的总内存大小 BIOS 版本 磁盘设备 vda 的大小 磁盘设备 vdb 的大小 输出文件中…

c语言练习题34:打印整数二进制的奇数位和偶数位

打印整数二进制的奇数位和偶数位 获取一个整数二进制序列中所有的偶数位和奇数位&#xff0c;分别打印出二进制序列 思路&#xff1a; 1. 提取所有的奇数位&#xff0c;如果该位是1&#xff0c;输出1&#xff0c;是0则输出0 2. 以同样的方式提取偶数位置检测num中某一位是0还…

【Java基础】Java注解与反射

文章目录 ⭐️写在前面的话⭐️1、什么是注解&#xff1f;注解的分类常用的Java注解 2、元注解TargetRetentionDocumentedInherited 3、自定义注解Override注解的基本格式 4、什么是反射&#xff1f;什么时候需要用到反射&#xff1f;反射的应用场合 5、反射的原理6、反射机制的…

大数据-玩转数据-Flink窗口函数

一、Flink窗口函数 前面指定了窗口的分配器, 接着我们需要来指定如何计算, 这事由window function来负责. 一旦窗口关闭, window function 去计算处理窗口中的每个元素. window function 可以是ReduceFunction,AggregateFunction,or ProcessWindowFunction中的任意一种. Reduc…