C++ STL(标准模板库)是C++重要的组成部分,我们c++程序员每天都在使用stl,对于c++11/14/17新标准。究竟stl底层是如何实现的?我们去探究一下。
C++11标准 是 C++ 语言发展史上具有里程碑意义的一个版本,掌握C++11/14以及以后得新标准是每个c++程序员基础技能!不能还停留在传统c++的知识层面上(C++98/03)。
std::thread的构造函数->c运行时库的线程函数_beginthreadex->WIN 32 API创建线程CreateThread
以下是调试具体的调用流程

备注:要向单步调试到_beginthreadex需要给模块ucrtbased.dll加载符号,此函数位于文件C:\Program Files (x86)\Windows Kits\10\Source\10.0.19041.0\ucrt\startup\thread.cpp
此函数的作用是阻塞等待线程执行结束。
实现原理:thead::join()->msvcp140d.dll!_Thrd_join() ->Win32 API WaitForSingleObjectEx 等待线程的执行结束

备注:要想命中_Thrd_join(),需要给msvcp140d加载符号!
就是不等待线程执行,主线程和工作者线程分离
实现原理:std::thread::detach()->msvcp140d.dll!_Thrd_detach()->Win32 API CloseHandle()

this_thread::sleep_for() 函数=>msvcp140d.dll!Thrd_sleep()函数=>win32api Sleep()

std::mutex的成员函数std::_Mutex_base::lock()->msvcp140d.dll!_Mtx_lock()->
msvcp140d.dll!Concurrency::details::stl_critical_section_win7::lock() ->
Win32 API 读写锁AcquireSRWLockExclusive

以下是测试代码:
std::mutex的成员函数std::_Mutex_base::unlock()->msvcp140d.dll!_Mtx_unlock()->msvcp140d.dll!Concurrency::details::stl_critical_section_win7::unlock()-> Win32 APIReleaseSRWLockExclusive

解说:条件变量,是Windows Vista中新增加的一种处理线程同步问题的机制。它可以与“关键代码段(critical section)”或“读写锁(SRWLock)”相互 配合使用,来实现线程的同步,特别是实现类似“生产者-消费者”问题 的时候,十分有效。
先说下条件变量的各个成员函数对应的Win 32 API:
std::condition_variable::wait() 对应win32 API: SleepConditionVariableSRW
std::condition_variable::notify_all 对应win32 API: WakeAllConditionVariable
std::condition_variable::notify_one 对应win32 API: WakeConditionVariable
wait() 函数底层调用的是win32 API: SleepConditionVariableSRW

notify_one()函数底层调用的是Win32 API:WakeConditionVariable

notify_all()函数底层调用的是Win32 API:WakeAllConditionVariable

以下是测试代码:
原子操作的实现原理值通过支持原子操作的汇编指令实现的(无锁编程Lock-free)。底层调用的是Win32 API的无锁函数
_InterlockedExchange、_InterlockedIncrement 等系列的函数,这种函数有很多个,可以自行查阅。
汇编指令原子操作方法,在汇编语言中,原子操作是只不能被中断或者同时执行多个指令的操作,原子操作通常用于对共享资源的读写操作,确保在多线程环境下数组的一致性。在x86架构下,汇编指令提供了一些原子操作的方法,例如:
以上是一些常用的汇编指令原子操作的方法,他们可以通过锁定指令总线或者其他的硬件机制来确保操作的原子性,不同的处理器和架构可能提供不同的原子操作方法。
有如下代码:
其调用过程如下,可看到nInt = nInt + 2;这一句代码底层调用的是Win32 API _InterlockedExchange(原子交换两个数的值)

那么Win32 API _InterlockedExchange是如何实现原子操作的?来看一下此函数对应的汇编代码:

通过上图可以看到 _InterlockedExchange函数用了支持原子操作的汇编指令xchg,来交换两个操作数的值。可以参考上面的表格
有如下代码:
其调用过程如下:可以看到nInt++这句代码调用的是Win32 API _InterlockedIncrement(原子加法操作)

那么Win32 API _InterlockedIncrement是如何实现原子操作的?来看一下此函数对应的汇编代码:

可以看到_InterlockedIncrement用到是原子加法汇编指令lock xadd,可以参考上面的表格。
万能引用的两个前提条件:一是函数模板的形参、二是类型为T&& val,否则大部分就是右值引用。
理解万能引用和引用折叠是理解完美转发std::forward和std::move的前提,所以这里首先搞明白这个概念。
关于引用折叠的核心是,C++11当中的一些构造会弄出来引用的引用,而C++不允许出现引用的引用。如果代码当中显示的出现了一个引用的引用,那代码就是不合法的,会出现编译错误。
当用rvalue 10调用 f 的时候, T被推导为 int,实例化的 f 看起来像这样:
这里一切都OK。但是当我们用lvalue x 来调用 f 的时候,T 被推导为int&,而实例化的 f 就包含了一个引用的引用:
因为这里出现了引用的引用,这实例化的代码乍一看好像不合法,但是像f(x)这么写代码是完全合理的。为了避免编译器对这个代码报错,C++11引入了一个叫做“引用折叠”(reference collapsing)的规则来处理某些像模板实例化这种情况下带来的"引用的引用"的问题。
"引用的引用"就有四种可能的组合:
a)左值引用的左值引用:int& & 折叠为 int&
b) 左值引用的右值引用:int& && 折叠为 int&
c) 右值引用的左值引用:int&& & 折叠为 int&
d) 右值引用的右值引用:int&& && 折叠为 int&&
引用折叠只有两条规则:
一个 rvalue reference to an rvalue reference 会变成 (“折叠为”) 一个 rvalue reference.
所有其他种类的"引用的引用" (组合当中含有lvalue reference) 都会折叠为 lvalue reference.
下面再来看引用一种引用折叠的情况,
当一个变量本身的类型是引用类型的时候,这里就有点难搞了。这种情况下,类型当中所带的引用就被忽略了。例如
代码如下:
在调用模板函数 f 的时候 r1 和 r2 的类型都被当做 int。这个扒掉引用的行为,和"universal references 在类型推导期间,lvalues 被推导为 T& ,rvalues 被推导为"T" 这条规则无关。所以,这么调用模板函数的时候r1 和r2 的类型都被推导为 int&。这是为啥呢?
首先,r1 和 r2 的引用部分被去掉了(留下的只是 int)。然后,因为它们都是 lvalues 所以当调用 f,对 universal reference 参数进行类型推导的时候,得到的类型都是int&。
std::forward是C++11引入的一个模板函数, 完美转发是C++中一种高级的技术,用于在函数模板中转发参数至另一个函数,同时保持所有参数的值类别(左值、右值)和其他属性(如const修饰符)不变。这一技术主要通过模板和std::forward实现,并在泛型编程中尤为重要,因为它允许函数模板在不丢失任何参数信息的前提下传递参数。
理解完美转发的要点:
仅在模板函数中,并且配合万能引用参数使用
维护参数属性的完整性:确保参数的const属性和值类别在转发过程中不被改变,这对于编写通用代码库和API尤为重要
完美转发可以避免在函数间传递时不必要的拷贝操作,优化性能。
下面看一个不完美转发的例子:
以上的例子,两次函数调用MiddleLayerFun,只会调用到调用到第一个函数void fun(int& x),不会调用到void fun(int&& x)
因为param是个形参,形参永远是左值,所以只会调用左值版本。
下面看一个完美转发的例子:
MiddleLayerFun(a),参数a是个左值,通过函数模板的MiddleLayerFun和完美转发调用左值版本的函数,
MiddleLayerFun(20)调用,参数20是个右值,通过函数模板的MiddleLayerFun和完美转发调用右值版本的函数。
上面就是完美转发。
std::forward 根据传入参数的类型在编译时确定返回左值引用或右值引用:
当传递给std::forward的参数是一个左值时,std::forward返回一个左值引用。
当传递给std::forward的参数是一个右值时,它返回一个右值引用。
这种行为使得std::forward非常适合用于函数模板中,尤其是那些需要根据参数原始类型将参数转发到其他函数的模板。
下面是标准库中完美转发的实现源码:
备注:上面的remove_reference_t的功能是:就是擦除类型引用,无论是左值引用还是右值引用,例如,如果_Ty是int&或int&&,那么std::remove_reference_t<_Ty>就是int
其源代码代码如下:
下面分析下完美转发的实现原理:
对于章节中2.6.1中完美转发的例子
对于MiddleLayerFun(a);这句调用,会有如下过程
step 1)实例化MiddleLayerFun
MiddleLayerFun(a)调用中,a是左值,T被推导为int&, 根据万能引用的实例化规则,实例化的MiddleLayerFun如下
引用折叠后,上面的代码变为:
备注:关于引用折叠,参考章节2.5
step 2)实例化std::forward
左值版本的std::forward定义如下:
由于Step1中调用std::forward<int &>,所以此处我们代入T = int &,有:
根据引用折叠规则,上面的代码,等价于:
纵上,所以最终static_cast<int&>(_Arg);的作用就是将参数_Arg强制转型成int &,而int &为左值。所以,调用左值版本的fun。
MiddleLayerFun(a)中a是左值,经过std::forward的完美转发。保证了实参a的参数类型和左右值属性不变。
下面分析章节中2.6.1中完美转发的例子
MiddleLayerFun(20);这句调用,会有如下过程
step 1)实例化MiddleLayerFun
因为参数20是个右值,所以T被推导为int类型
备注:在实例化的MiddleLayerFun函数中,右值引用变量param是个左值(形参永远都是左值),所以还是会调用左值版本的std::forword
step 2)实例化std::forward
左值版本的std::forward定义如下:
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2024-11-7 12:06
被sanganlei编辑
,原因: