C++:std::function的libc++实现
std::function
是个有点神奇的模板,无论是普通函数、函数对象、lambda表达式还是std::bind
的返回值(以上统称为可调用对象(Callable)),无论可调用对象的实际类型是什么,无论是有状态的还是无状态的,只要它们有相同参数类型和返回值类型,就可以使用同一类型的std::function
进行存储和调用。这种特性被称作类型擦除(Type erasure),它允许我们在不知道对象实际类型的情况下对对象进行存储和操作。
在本文中,我将以std::function
的libc++
实现(14.0版本)为例,分析std::function
类型擦除的实现原理,以及实现一个精简版的std::function
:MyFunction
。
std::function
如何实现类型擦除?
在不知道对象实际类型的情况下操作对象,有一种常规的手段可以实现这个功能,那就是多态,libc++
版的std::function
正是基于虚函数实现的。具体是如何实现的呢?我们可以从考察std::function
在被调用时发生了什么作为这个问题的切入点。
对于以下代码:
#include <functional>
#include <iostream>
int main() {std::function<void()> f = []() { //std::cout << "Hello, world!" << std::endl;};f();return 0;
}
在std::cout
一行打断点,运行,得到以下堆栈:
#0 main::$_0::operator() (this=0x7fffffffdb18) at /mnt/d/code/function_test/call.cpp:6
#1 0x0000555555557745 in std::__1::__invoke<main::$_0&> (__f=...) at /usr/lib/llvm-14/bin/../include/c++/v1/type_traits:3640
#2 0x00005555555576fd in std::__1::__invoke_void_return_wrapper<void, true>::__call<main::$_0&> (__args=...) at /usr/lib/llvm-14/bin/../include/c++/v1/__functional/invoke.h:61
#3 0x00005555555576cd in std::__1::__function::__alloc_func<main::$_0, std::__1::allocator<main::$_0>, void ()>::operator()() (this=0x7fffffffdb18) at /usr/lib/llvm-14/bin/../include/c++/v1/__functional/function.h:180
#4 0x0000555555556839 in std::__1::__function::__func<main::$_0, std::__1::allocator<main::$_0>, void ()>::operator()() (this=0x7fffffffdb10) at /usr/lib/llvm-14/bin/../include/c++/v1/__functional/function.h:354
#5 0x0000555555558622 in std::__1::__function::__value_func<void ()>::operator()() const (this=0x7fffffffdb10) at /usr/lib/llvm-14/bin/../include/c++/v1/__functional/function.h:507
#6 0x00005555555577d5 in std::__1::function<void ()>::operator()() const (this=0x7fffffffdb10) at /usr/lib/llvm-14/bin/../include/c++/v1/__functional/function.h:1184
#7 0x00005555555562e5 in main () at /mnt/d/code/function_test/call.cpp:8
不考虑lambda本身,以及invoke
相关的类,std::function
实现相关的类有以下几个:
std::__1::function<void ()>
std::__1::__function::__value_func<void ()>
std::__1::__function::__func<main::$_0, std::__1::allocator<main::$_0>, void ()>
std::__1::__function::__alloc_func<main::$_0, std::__1::allocator<main::$_0>, void ()>
lambda的类型被定义为了main::$_0
,可以看出来,function
和__function::__value_func
两个模板类不依赖lambda实际类型,__function::__func
和__function::__alloc_func
对lambda类型有依赖。
std::function
从std::function
看起,被声明为拥有一个模板参数_Fp
。我们使用的是它的特化版本,具有两个模板参数,返回值类型_Rp
和参数列表类型_ArgTypes
(接下来几个类也都是特化出来的,不再赘述)。它有一个__function::__value_func<_Rp(_ArgTypes...)>
类型的成员__f_
:
template<class _Fp> class function;template<class _Rp, class ..._ArgTypes>
class function<_Rp(_ArgTypes...)>
{typedef __function::__value_func<_Rp(_ArgTypes...)> __func;__func __f_;...
};
...
template <class _Rp, class... _ArgTypes>
template <class _Fp, class>
function<_Rp(_ArgTypes...)>::function(_Fp __f) : __f_(_VSTD::move(__f)) {}
当std::function
的operator()
被调用时,它只是地把调用转发给__f_
:
template<class _Rp, class ..._ArgTypes>
_Rp
function<_Rp(_ArgTypes...)>::operator()(_ArgTypes... __arg) const
{return __f_(_VSTD::forward<_ArgTypes>(__arg)...);
}
__function::__value_func
看看__function::__value_func
具体是什么类型:
// __value_func creates a value-type from a __func.
template <class _Fp> class __value_func;template <class _Rp, class... _ArgTypes> class __value_func<_Rp(_ArgTypes...)>
{typename aligned_storage<3 * sizeof(void*)>::type __buf_;typedef __base<_Rp(_ArgTypes...)> __func;__func* __f_;...
};
它的模板参数和std::function
一致,有两个成员,一个成员是有3个指针大小的__buf_
,另一个成员是__function::__base<_Rp(_ArgTypes...)>*
类型的__f_
。
__function::__value_func
的构造函数相对复杂一些,主要是为了做一个优化:当__f_
指向的对象的大小小于等于__buf_
的大小,也就是3个指针时,__f_
会被构造在__buf_
上,这样可以减少堆上内存的分配:
template <class _Fp, class _Alloc>
__value_func(_Fp&& __f, const _Alloc& __a): __f_(nullptr)
{typedef allocator_traits<_Alloc> __alloc_traits;typedef __function::__func<_Fp, _Alloc, _Rp(_ArgTypes...)> _Fun;typedef typename __rebind_alloc_helper<__alloc_traits, _Fun>::type_FunAlloc;if (__function::__not_null(__f)){_FunAlloc __af(__a);if (sizeof(_Fun) <= sizeof(__buf_) &&is_nothrow_copy_constructible<_Fp>::value &&is_nothrow_copy_constructible<_FunAlloc>::value){__f_ =::new ((void*)&__buf_) _Fun(_VSTD::move(__f), _Alloc(__af));}else{typedef __allocator_destructor<_FunAlloc> _Dp;unique_ptr<__func, _Dp> __hold(__af.allocate(1), _Dp(__af, 1));::new ((void*)__hold.get()) _Fun(_VSTD::move(__f), _Alloc(__a));__f_ = __hold.release();}}
}
需要注意到的一个细节是:__f_
在模板类定义中的类型是__function::__base
,而此处new
出来的对象类型是__function::__func
,不难猜到,__function::__func
继承了__function::__base
。
当__function::__value_func
的operator()
被调用时,它也只是在做完合法性检查后把调用转发给了*__f_
:
_Rp operator()(_ArgTypes&&... __args) const
{if (__f_ == nullptr)__throw_bad_function_call();return (*__f_)(_VSTD::forward<_ArgTypes>(__args)...);
}
__function::__base
下面是__function::__base
,它是一个抽象模板类,模板参数和std::function
一致,不包含可调用对象的具体类型:
template<class _Fp> class __base;template<class _Rp, class ..._ArgTypes>
class __base<_Rp(_ArgTypes...)>
{__base(const __base&);__base& operator=(const __base&);
public:_LIBCPP_INLINE_VISIBILITY __base() {}_LIBCPP_INLINE_VISIBILITY virtual ~__base() {}virtual __base* __clone() const = 0;virtual void __clone(__base*) const = 0;virtual void destroy() _NOEXCEPT = 0;virtual void destroy_deallocate() _NOEXCEPT = 0;virtual _Rp operator()(_ArgTypes&& ...) = 0;
#ifndef _LIBCPP_NO_RTTIvirtual const void* target(const type_info&) const _NOEXCEPT = 0;virtual const std::type_info& target_type() const _NOEXCEPT = 0;
#endif // _LIBCPP_NO_RTTI
};
__function::__func
然后是__function::__func
,它继承了__function::__base
,并且其模板参数含有可调用对象的类型_Fp
,这正是实现类型擦除的关键:类型_Fp
被隐藏了在了__function::__base
这个抽象类后面。__function::__func
含有一个类型为__function::__alloc_func
的成员__f_
:
// __func implements __base for a given functor type.
template<class _FD, class _Alloc, class _FB> class __func;template<class _Fp, class _Alloc, class _Rp, class ..._ArgTypes>
class __func<_Fp, _Alloc, _Rp(_ArgTypes...)>: public __base<_Rp(_ArgTypes...)>
{__alloc_func<_Fp, _Alloc, _Rp(_ArgTypes...)> __f_;
public:explicit __func(_Fp&& __f): __f_(_VSTD::move(__f)) {}...
};
__function::__func
的operator()
依然只是转发调用:
template<class _Fp, class _Alloc, class _Rp, class ..._ArgTypes>
_Rp
__func<_Fp, _Alloc, _Rp(_ArgTypes...)>::operator()(_ArgTypes&& ... __arg)
{return __f_(_VSTD::forward<_ArgTypes>(__arg)...);
}
__function::__alloc_func
然后是最后一个类__function::__alloc_func
,它有一个pair
类型的成员__f_
,std::function
构造时传入的可调用对象最终会存储在__f_
中:
// __alloc_func holds a functor and an allocator.
template <class _Fp, class _Ap, class _FB> class __alloc_func;template <class _Fp, class _Ap, class _Rp, class... _ArgTypes>
class __alloc_func<_Fp, _Ap, _Rp(_ArgTypes...)>
{__compressed_pair<_Fp, _Ap> __f_;public:...explicit __alloc_func(_Target&& __f): __f_(piecewise_construct, _VSTD::forward_as_tuple(_VSTD::move(__f)),_VSTD::forward_as_tuple()){}...
};
在__function::__alloc_func
的operator()
方法中,调用转发给了__invoke_void_return_wrapper::__call
,后面的流程就和std::function
的实现无关了。
_Rp operator()(_ArgTypes&&... __arg)
{typedef __invoke_void_return_wrapper<_Rp> _Invoker;return _Invoker::__call(__f_.first(),_VSTD::forward<_ArgTypes>(__arg)...);
}
最终我们发现,“神奇”的类型擦除还是通过“朴素”的多态来实现的,之所以显得神奇是因为多态被隐藏了起来,没有暴露给用户。
std::function
对构造参数的校验
仔细观察一下std::function
的构造函数:
template <class _Rp, class... _ArgTypes>
template <class _Fp, class>
function<_Rp(_ArgTypes...)>::function(_Fp __f) : __f_(_VSTD::move(__f)) {}
构造函数对参数__f
似乎并没有施加任何约束,如何真是那样,那我们在使用一个不恰当的_Fp
类型构造std::function
时,很可能会得到可读性极差的编译错误信息,因为std::function
类本身对_Fp
没有施加约束,那么实例化std::function
时也就不太可能出现错误了,很有可能到了实例化__function::__alloc_func
时编译错误才会报告出来,这是一个内部类,一般用户看到了关于它的实例化失败的错误信息大概会感到摸不着头脑。
但实际情况并不是这样的,假设你这样定义一个std::function
对象:
std::function<void()> f(1);
你会得到一个比较清晰的编译错误信息:
/mnt/d/code/function_test/myfunction.cpp:107:27: error: no matching constructor for initialization of 'std::function<void ()>'std::function<void()> f(1);
...
/usr/lib/llvm-14/bin/../include/c++/v1/__functional/function.h:998:5: note: candidate template ignored: requirement '__callable<int &, false>::value' was not satisfied [with _Fp = int]function(_Fp);
...
这是怎么做到的呢?答案藏在构造函数声明的第二个模板参数class = _EnableIfLValueCallable<_Fp>
:
template<class _Fp, class = _EnableIfLValueCallable<_Fp>>
function(_Fp);
此处使用了SFINAE技术,我们看看_EnableIfLValueCallable
具体是怎么实现的:
template <class _Fp, bool = _And<_IsNotSame<__uncvref_t<_Fp>, function>,__invokable<_Fp, _ArgTypes...>
>::value>
struct __callable;
template <class _Fp>struct __callable<_Fp, true>{static const bool value = is_void<_Rp>::value ||__is_core_convertible<typename __invoke_of<_Fp, _ArgTypes...>::type,_Rp>::value;};
template <class _Fp>struct __callable<_Fp, false>{static const bool value = false;};template <class _Fp>
using _EnableIfLValueCallable = typename enable_if<__callable<_Fp&>::value>::type;
_EnableIfLValueCallable
的实现依赖于__callable
,__callable
是一个模板类,拥有两个模板参数,第一个模板参数_Fp
是可调用对象的类型,第二个模板参数是bool
类型的,当_IsNotSame<__uncvref_t<_Fp>, function>
和__invokable<_Fp, _ArgTypes...>
这两个条件同时满足时,该模板参数为true,否则为false。
_IsNotSame<__uncvref_t<_Fp>, function>
,顾名思义,是用来判断两个模板参数是否为同一类型的,这个条件似乎是为了避免歧义:当我们用另一个std::function
构造std::function
时,应该匹配到拷贝构造函数,而不是这个。
__invokable<_Fp, _ArgTypes...>
则是用来判断_Fp
是否接受传入_ArgTypes
参数调用。
__callable
第二个模板参数为false
的特化中,将value
直接定义为false
。而模板参数为true
的特化中,还添加了新的判断条件,用来校验可调用对象返回值的可转换性。
第一个条件为is_void<_Rp>::value
,用来判断_Rp
为void
类型。这意味着,即使可调用对象实际上有返回类型,但是std::function
被定义为返回void
,那么编译也是可以通过的。
第二个条件是__is_core_convertible<typename __invoke_of<_Fp, _ArgTypes...>::type, _Rp>::value
,用来判断_Fp
被调用后返回值可转换为_Rp
。
综上,_Fp
要满足以下条件,std::function
的构造函数才能正常实例化:
_Fp
不是std::function
&& _Fp
可以以_ArgTypes
为参数调用 && (_Rp
为void
|| _Fp
返回值类型可转换为_Rp
)
这保证了当以不恰当的可调用对象构造std::function
时,能够尽可能提前触发编译错误,提升编译错误信息的可读性。
MyFunction的实现
下面我们下面模仿libc++
,实现一个“青春版”的std::function
:MyFunction
,它忽略掉了大部分细节,只实现了构造和调用部分的代码。
#include <functional>
#include <iostream>
#include <utility>template <typename Func>
class FunctionBase;template <typename Ret, typename... Args>
class FunctionBase<Ret(Args...)> {public:virtual Ret operator()(Args&&... args) = 0;
};template <typename Callable, typename Func>
class FunctionImpl;template <typename Callable, typename Ret, typename... Args>
class FunctionImpl<Callable, Ret(Args...)> : public FunctionBase<Ret(Args...)> {Callable c_;public:FunctionImpl(Callable&& c) : c_(std::move(c)) {}Ret operator()(Args&&... args) override {return std::invoke(c_, std::forward<Args>(args)...);}
};template <typename Func>
class MyFunction;template <typename Ret, typename... Args>
class MyFunction<Ret(Args...)> {FunctionBase<Ret(Args...)>* f_ = nullptr;public:template <typename Callable>MyFunction(Callable c) {f_ = new FunctionImpl<Callable, Ret(Args...)>(std::move(c));}Ret operator()(Args&&... args) {if (f_ == nullptr) {throw std::bad_function_call();}return (*f_)(std::forward<Args>(args)...);}
};void normalFunction() { std::cout << "I'm a normal function" << std::endl; }struct FunctionObject {void operator()() { std::cout << "I'm a function object" << std::endl; }
};int main() {MyFunction<void()> f0 = []() { std::cout << "I'm a lambda" << std::endl; };f0();MyFunction<void()> f1 = normalFunction;f1();MyFunction<void()> f2 = FunctionObject();f2();return 0;
}
结语
在没有std::function
可用的年代或者场合,我们一般会选择使用函数指针来实现类似std::function
的功能。在使用C
实现的Linux
内核代码中,我们仍可以看到大量的函数指针的存在,主要是用来实现回调函数。
相较函数指针,std::function
最明显的优势在于可以方便地存储带状态的函数,而函数指针只能以比较丑陋的方式来实现这个特性。
其次是灵活性,std::function
给客户代码施加的约束较小,我们可以使用任意形式的可调用对象:普通函数,lambda表达式,函数对象等,函数指针就没有这种灵活性了。
不过由于虚函数的存在,std::function
多了一点性能开销,但这点开销对大多数常规应用来说都是微不足道的。
相关文章:
C++:std::function的libc++实现
std::function是个有点神奇的模板,无论是普通函数、函数对象、lambda表达式还是std::bind的返回值(以上统称为可调用对象(Callable)),无论可调用对象的实际类型是什么,无论是有状态的还是无状态…...

DM 的断点续传测试
作者: 大鱼海棠 原文来源: https://tidb.net/blog/4540ae34 一、概述 DM有all、full、incremental三种数据迁移同步方式(task-mode),在all同步模式下,因一些特殊情况,需要变更上游MySQL的数…...

力扣每日一题 6/30 记忆化搜索/动态规划
博客主页:誓则盟约系列专栏:IT竞赛 专栏关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ 494.目标和【中等】 题目: 给你一个非负整数数组 nums 和一个…...

图像基础知识入门【图像概念不同图像格式】
图像基础知识入门【图像概念&不同图像格式】 最近有在处理图像转换,因此稍微补足了一下图像相关知识,特在此记录。下面汇总是我根据自己理解和网上查阅资料而来。如有错误,欢迎大家指正。 1 基础概念 像素/分辨率 像素(Pixel)ÿ…...
HP服务器基于SNMP-ilo4的硬件监控指标解读
监控易是一款功能全面的IT基础设施监控软件,它通过SNMP协议与HP服务器内置的ilo4远程管理卡进行通信,实现对HP服务器硬件状态的实时监控。本文将针对监控易中基于SNMP-ilo4的HP服务器硬件监控指标进行解读,帮助运维团队更好地理解和应用这些监…...
Android13系统导航栏添加音量加减键按钮功能
不知道为什么拿到芯片原厂发布给我们的Android13系统源码编译后,导航栏没有音量加减键,客户有反馈这个问题,所以特意加了一下,修改记录如下:frameworks/base目录下 commit 9cb2244d61a237cab03c540bfcca6e4fac2bea2c …...

普及GIS知识,推动产业发展
915 GIS节:普及GIS知识,推动产业发展 自2008年起,每年的9月15日被定为“GIS节”,这一特殊的节日由超图首次发起倡议,旨在打造一个普及和传播GIS(地理信息系统)知识的平台,促进大众对…...

第2章-Python编程基础
#本章目标 1,了解什么是计算机程序 2,了解什么是编程语言 3,了解编程语言的分类 4,了解静态语言与脚本语言的区别 5,掌握IPO程序编写方法 6,熟练应用输出函数print与输入函数input 7,掌握Python…...

LDO产品的基础知识解析
低压降稳压器 (LDO)是一种用于调节较高电压输入产生的输出电压的简单方法。在大多数情况下,低压降稳压器都易于设计和使用。然而,如今的现代应用都包括各种各样的模拟和数字系统,而有些系统和工作条件将决定哪种LDO最适合相关电路,…...

如何利用python画出AHP-SWOT的战略四边形(四象限图)
在企业或产业发展的相关论文分析中,常用到AHP-SWOT法进行定量分析,形成判断矩阵后,如何构造整洁的战略四边形是分析的最后一个环节,本文现将相关代码发布如下: import mpl_toolkits.axisartist as axisartist import …...

适用于智慧城市、智慧文旅等在线场景的轻量级3D数字人引擎MyAvatar简介
本人研发的国内首个纯面向web应用和小程序的轻量级3D虚拟人引擎MyAvatar。 功能简述 支持3D模型定制(写实或卡通风格均可,人物模型需实现绑定和变形)动画可以内置于模型中,也可以单独以glb或fbx格式导出并动态加载支持readyplay…...

Excel显示/隐藏批注按钮为什么是灰色?
在excel中,经常使用批注来加强数据信息的提示,有时候会把很多的批注显示出来,但是再想将它们隐藏起来,全选工作表后,“显示/隐藏批注”按钮是灰色的,不可用。 二、可操作方法 批注在excel、WPS表格中都是按…...

ArtTS系统能力-通知的学习(3.1)
上篇回顾: ArtTS语言基础类库-容器类库内容的学习(2.10.2) 本篇内容: ArtTS系统能力-通知的学习(3.1) 一、 知识储备 1. 基础类型通知 按内容分成四类: 类型描述NOTIFICATION_CONTENT_BASIC_TEXT普通文…...
Apollo9.0 PNC源码学习之Planning模块(三)—— public_road_planner
前面文章: (1)Apollo9.0 PNC源码学习之Planning模块(一)—— 规划概览 (2)Apollo9.0 PNC源码学习之Planning模块(二)—— planning_component 1 planning_interface_base 规划接口基类: planning\planning_interface_base\planner_base\planner.h #pragma once#in…...

【Elasticsearch】linux使用supervisor常驻Elasticsearch,centos6.10安装 supervisor
背景: linux服务器,CentOS 6操作系统,默认版本python2.6.6,避免安装过多的依赖不升级python 在网上查的资料python2.6.6兼容supervisor版本 3.1.3 安装supervisor 手动在python官网下载supervisor,并上传到服务器 下…...

推荐系统三十六式学习笔记:原理篇.模型融合14|一网打尽协同过滤、矩阵分解和线性模型
目录 从特征组合说起FM模型1.原理2.模型训练3.预测阶段4.一网打尽其他模型5.FFM 总结 在上一篇文章中,我们讲到了使用逻辑回归和梯度提升决策树组合的模型融合办法,用于CTR预估,给这个组合起了个名字,叫“辑度组合”。这对组合中&…...

如何使用mapXplore将SQLMap数据转储到关系型数据库中
关于mapXplore mapXplore是一款功能强大的SQLMap数据转储与管理工具,该工具基于模块化的理念开发,可以帮助广大研究人员将SQLMap数据提取出来,并转储到类似PostgreSQL或SQLite等关系型数据库中。 功能介绍 当前版本的mapXplore支持下列功能…...

JAVA设计模式-大集合数据拆分
背景 我们在做软件开发时,经常会遇到把大集合的数据,拆分成子集合处理。例如批量数据插入数据库时,一次大约插入5000条数据比较合理,但是有时候待插入的数据远远大于5000条。这时候就需要进行数据拆分。数据拆分基本逻辑并不复杂&…...

如何使用sr2t将你的安全扫描报告转换为表格格式
关于sr2t sr2t是一款针对安全扫描报告的格式转换工具,全称为“Scanning reports to tabular”,该工具可以获取扫描工具的输出文件,并将文件数据转换为表格格式,例如CSV、XLSX或文本表格等,能够为广大研究人员提供一个…...

ansible自动化运维,(2)ansible-playbook
三种常见的数据格式: XML:可扩展标记语言,用于数据交换和配置 JSON:对象标记法,主要用来数据交换或配置,不支持注释 YAML:不是一种标记语言,主要用来配置,大小写敏感&…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...