转自:http://blog.csdn.net/innost/article/details/6752443
5.1 概述
初次接触Android源码时,见到最多的一定是sp和wp。即使你只是沉迷于Java世界的编码,那么Looper和Handler也是避不开的。本章的目的,就是把经常碰到的这些内容中的“拦路虎”一网打尽,将它们彻底搞懂。至于弄明白它们有什么好处,就仁者见仁,智者见智了。个人觉得Looper和Handler相对会更实用一些。
5.2 以“三板斧”揭秘RefBase、sp和wp
RefBase是Android中所有对象的始祖,类似于MFC中的CObject及Java中的Object对象。在Android中,RefBase结合sp和wp,实现了一套通过引用计数的方法来控制对象生命周期的机制。就如我们想像的那样,这三者的关系非常暧昧。初次接触Android源码的人往往会被那个随处可见的sp和wp搞晕了头。
什么是sp和wp呢?其实,sp并不是我开始所想的smart pointer(C++语言中有这个东西),它真实的意思应该是strong pointer,而wp则是weak pointer的意思。我认为,Android推出这一套机制可能是模仿Java,因为Java世界中有所谓weak reference之类的东西。sp和wp的目的,就是为了帮助健忘的程序员回收new出来的内存。
说明 我还是喜欢赤裸裸地管理内存的分配和释放。不过,目前sp和wp的使用已经深入到Android系统的各个角落,想把它去掉真是不太可能了。
这三者的关系比较复杂,都说程咬金的“三板斧”很厉害,那么我们就借用这三板斧,揭密其间的暧昧关系。
5.2.1 第一板斧—初识影子对象
我们的“三板斧”,其实就是三个例子。相信这三板斧劈下去,你会很容易理解它们。
[-->例子1]
//类A从RefBase派生,RefBase是万物的始祖。
class A:public RefBase
{
//A没有任何自己的功能。
}
int main()
{
A* pA = new A;
{
//注意我们的sp、wp对象是在{}中创建的,下面的代码先创建sp,然后创建wp。
sp<A> spA(pA);
wp<A> wpA(spA);
//大括号结束前,先析构wp,再析构sp。
}
}
例子够简单吧?但也需一步一步分析这斧子是怎么劈下去的。
1. RefBase和它的影子
类A从RefBase中派生。使用的是RefBase构造函数。代码如下所示:
[-->RefBase.cpp]
RefBase::RefBase()
: mRefs(new weakref_impl(this))//注意这句话
{
//mRefs是RefBase的成员变量,类型是weakref_impl,我们暂且叫它影子对象。
//所以A有一个影子对象。
}
mRefs是引用计数管理的关键类,需要进一步观察。它是从RefBase的内部类weakref_type中派生出来的。
先看看它的声明:
class RefBase::weakref_impl : public RefBase::weakref_type
//从RefBase的内部类weakref_type派生。
由于Android频繁使用C++内部类的方法,所以初次阅读Android代码时可能会有点不太习惯,C++的内部类和Java的内部类相似,但有一点不同,即它需要一个显式的成员指向外部类对象,而Java的内部类对象有一个隐式的成员指向外部类对象的。
说明 内部类在C++中的学名叫nested class(内嵌类)。
[-->RefBase.cpp::weakref_imple构造]
weakref_impl(RefBase* base)
: mStrong(INITIAL_STRONG_VALUE) //强引用计数,初始值为0x1000000。
, mWeak(0) //弱引用计数,初始值为0。
, mBase(base)//该影子对象所指向的实际对象。
, mFlags(0)
, mStrongRefs(NULL)
, mWeakRefs(NULL)
, mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
, mRetain(false)
{
}
如你所见,new了一个A对象后,其实还new了一个weakref_impl对象,这里称它为影子对象,另外我们称A为实际对象。
这里有一个问题:影子对象有什么用?
可以仔细想一下,是不是发现影子对象成员中有两个引用计数?一个强引用,一个弱引用。如果知道引用计数和对象生死有些许关联的话,就容易想到影子对象的作用了。
说明 按上面的分析来看,在构造一个实际对象的同时,还会悄悄地构造一个影子对象,在嵌入式设备的内存不是很紧俏的今天,这个影子对象的内存占用已经不成问题了。
2.sp上场
程序继续运行,现在到了:
sp<A> spA(pA);
请看sp的构造函数,它的代码如下所示(注意,sp是一个模板类,对此不熟悉的读者可以去翻翻书,或者干脆把所有出现的T都换成A):
[-->RefBase.h::sp(T* other)]
template<typename T>
sp<T>::sp(T* other) //这里的other就是刚才创建的pA。
: m_ptr(other)// sp保存了pA的指针。
{
if (other) other->incStrong(this);//调用pA的incStrong。
}
OK,战场转到RefBase的incStrong中。它的代码如下所示:
[-->RefBase.cpp]
void RefBase::incStrong(const void* id) const
{
//mRefs就是刚才在RefBase构造函数中new出来的影子对象。
weakref_impl* const refs = mRefs;
//操作影子对象,先增加弱引用计数。
refs->addWeakRef(id);
refs->incWeak(id);
......
先来看看影子对象的这两个weak函数都干了些什么。
(1)眼见而心不烦
下面看看第一个函数addWeakRef,代码如下所示:
[-->RefBase.cpp]
void addWeakRef(const void* /*id*/) { }
呵呵,addWeakRef啥都没做,因为这是release版走的分支。调试版的代码我们就不讨论了,它是给创造RefBase、 sp,以及wp的人调试用的。
说明 调试版分支的代码很多,看来创造它们的人也在为不理解它们之间的暧昧关系痛苦不已。
总之,一共有这么几个不用考虑的函数,下面都已列出来了。以后再碰见它们,干脆就直接跳过去:
void addStrongRef(const void* /*id*/) { }
void removeStrongRef(const void* /*id*/) { }
void addWeakRef(const void* /*id*/) { }
void removeWeakRef(const void* /*id*/) { }
void printRefs() const { }
void trackMe(bool, bool) { }
继续我们的征程。再看incWeak函数,代码如下所示:
[-->RefBase.cpp]
void RefBase::weakref_type::incWeak(const void* id)
{
weakref_impl* const impl = static_cast<weakref_impl*>(this);
impl->addWeakRef(id); //上面说了,非调试版什么都不干。
const int32_t c = android_atomic_inc(&impl->mWeak);
//原子操作,影子对象的弱引用计数加1。
//千万记住影子对象的强弱引用计数的值,这是彻底理解sp和wp的关键。
}
好,我们再回到incStrong,继续看代码:
[-->RefBase.cpp]
......
//刚才增加了弱引用计数。
//再增加强引用计数。
refs->addStrongRef(id); //非调试版这里什么都不干。
//下面函数为原子加1操作,并返回旧值。所以c=0x1000000,而mStrong变为0x1000001。
const int32_t c = android_atomic_inc(&refs->mStrong);
if (c != INITIAL_STRONG_VALUE) {
//如果c不是初始值,则表明这个对象已经被强引用过一次了。
return;
}
//下面这个是原子加操作,相当于执行refs->mStrong +(-0x1000000),最终mStrong=1。
android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);
/*
如果是第一次引用,则调用onFirstRef,这个函数很重要,派生类可以重载这个函数,完成一些
初始化工作。
*/
const_cast<RefBase*>(this)->onFirstRef();
}
说明 android_atomic_xxx是Android平台提供的原子操作函数,原子操作函数是多线程编程中的常见函数,读者可以学习原子操作函数的相关知识,本章后面也会对其进行介绍。
(2)sp构造的影响
sp构造完后,它给这个世界带来了什么?
那就是在RefBase中影子对象的强引用计数变为1,且弱引用计数也变为1。
更准确的说法是,sp的出生导致影子对象的强引用计数加1,且弱引用计数也加1。
(3)wp构造的影响
继续看wp,例子中的调用方式如下:
wp<A> wpA(spA)
wp有好几个构造函数,原理都一样。来看这个最常见的:
[-->RefBase.h::wp(const sp<T>& other)]
template<typename T>
wp<T>::wp(const sp<T>& other)
: m_ptr(other.m_ptr) //wp的成员变量m_ptr指向实际对象。
{
if (m_ptr) {
//调用pA的createWeak,并且保存返回值到成员变量m_refs中。
m_refs = m_ptr->createWeak(this);
}
}
[-->RefBase.cpp]
RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
//调用影子对象的incWeak,这个我们刚才讲过了,它会导致影子对象的弱引用计数增加1。
mRefs->incWeak(id);
return mRefs; //返回影子对象本身。
}
我们可以看到,wp化后,影子对象的弱引用计数将增加1,所以现在弱引用计数为2,而强引用计数仍为1。另外,wp中有两个成员变量,一个保存实际对象,另一个保存影子对象。sp只有一个成员变量,用来保存实际对象,但这个实际对象内部已包含了对应的影子对象。
OK,wp创建完了,现在开始进行wp的析构。
(4)wp析构的影响
wp进入析构函数,则表明它快要离世了,代码如下所示:
[-->RefBase.h]
template<typename T>
wp<T>::~wp()
{
if (m_ptr) m_refs->decWeak(this); //调用影子对象的decWeak,由影子对象的基类实现。
}
[-->RefBase.cpp]
void RefBase::weakref_type::decWeak(const void* id)
{
//把基类指针转换成子类(影子对象)的类型,这种做法有些违背面向对象编程的思想。
weakref_impl* const impl = static_cast<weakref_impl*>(this);
impl->removeWeakRef(id);//非调试版不做任何事情。
//原子减1,返回旧值,c=2,而弱引用计数从2变为1。
const int32_t c = android_atomic_dec(&impl->mWeak);
if (c != 1) return; //c=2,直接返回。
//如果c为1,则弱引用计数为0,这说明没用弱引用指向实际对象,需要考虑是否释放内存。
// OBJECT_LIFETIME_XXX和生命周期有关系,我们后面再说。
if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
if (impl->mStrong == INITIAL_STRONG_VALUE)
delete impl->mBase;
else {
delete impl;
}
} else {
impl->mBase->onLastWeakRef(id);
if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {
delete impl->mBase;
}
}
}
在例1中,wp析构后,弱引用计数减1。但由于此时强引用计数和弱引用计数仍为1,所以没有对象被干掉,即没有释放实际对象和影子对象占据的内存。
(5)sp析构的影响
下面进入sp的析构。
[-->RefBase.h]
template<typename T>
sp<T>::~sp()
{
if (m_ptr) m_ptr->decStrong(this); //调用实际对象的decStrong,由RefBase实现。
}
[-->RefBase.cpp]
void RefBase::decStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
refs->removeStrongRef(id);//调用影子对象的removeStrongRef,啥都不干。
//注意,此时强弱引用计数都是1,下面函数调用的结果是c=1,强引用计数为0。
const int32_t c = android_atomic_dec(&refs->mStrong);
if (c == 1) { //对于我们的例子, c为1
//调用onLastStrongRef,表明强引用计数减为0,对象有可能被delete。
const_cast<RefBase*>(this)->onLastStrongRef(id);
//mFlags为0,所以会通过delete this把自己干掉。
//注意,此时弱引用计数仍为1。
if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
delete this;
}
......
}
先看delete this的处理,它会导致A的析构函数被调用。再来看A的析构函数,代码如下所示:
[-->例子1::~A()]
//A的析构直接导致进入RefBase的析构。
RefBase::~RefBase()
{
if (mRefs->mWeak == 0) { //弱引用计数不为0,而是1。
delete mRefs;
}
}
RefBase的delete this自杀行为没有把影子对象干掉,但我们还在decStrong中,可从delete this接着往下看:
[-->RefBase.cpp]
.... //接前面的delete this
if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
delete this;
}
//注意,实际数据对象已经被干掉了,所以mRefs也没有用了,但是decStrong刚进来
//的时候就把mRefs保存到refs了,所以这里的refs指向影子对象。
refs->removeWeakRef(id);
refs->decWeak(id);//调用影子对象decWeak
}
[-->RefBase.cpp]
void RefBase::weakref_type::decWeak(const void* id)
{
weakref_impl* const impl = static_cast<weakref_impl*>(this);
impl->removeWeakRef(id);//非调试版不做任何事情。
//调用前影子对象的弱引用计数为1,强引用计数为0,调用结束后c=1,弱引用计数为0。
const int32_t c = android_atomic_dec(&impl->mWeak);
if (c != 1) return;
//这次弱引用计数终于变为0了,并且mFlags为0, mStrong也为0。
if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
if (impl->mStrong == INITIAL_STRONG_VALUE)
delete impl->mBase;
else {
delete impl; //impl就是this,把影子对象也就是自己干掉。
}
} else {
impl->mBase->onLastWeakRef(id);
if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {
delete impl->mBase;
}
}
}
好,第一板斧劈下去了!来看看它的结果是什么。
3.第一板斧的结果
第一板斧过后,来总结一下刚才所学的知识:
RefBase中有一个隐含的影子对象,该影子对象内部有强弱引用计数。
sp化后,强弱引用计数各增加1,sp析构后,强弱引用计数各减1。
wp化后,弱引用计数增加1,wp析构后,弱引用计数减1。
完全彻底地消灭RefBase对象,包括让实际对象和影子对象灭亡,这些都是由强弱引用计数控制的,另外还要考虑flag的取值情况。当flag为0时,可得出如下结论:
强引用为0将导致实际对象被delete。
弱引用为0将导致影子对象被delete。
5.2.2 第二板斧—由弱生强
再看第二个例子,代码如下所示:
[-->例子2]
int main()
{
A *pA = new A();
wp<A> wpA(pA);
sp<A> spA = wpA.promote();//通过promote函数,得到一个sp。
}
对A的wp化,不再做分析了。按照前面所讲的知识,wp化后仅会使弱引用计数加1,所以此处wp化的结果是:
影子对象的弱引用计数为1,强引用计数仍然是初始值0x1000000。
wpA的promote函数是从一个弱对象产生一个强对象的重要函数,试看—
1. 由弱生强的方法
代码如下所示:
[-->RefBase.h]
template<typename T>
sp<T> wp<T>::promote() const
{
return sp<T>(m_ptr, m_refs); //调用sp的构造函数。
}
[-->RefBase.h]
template<typename T>
sp<T>::sp(T* p, weakref_type* refs)
: m_ptr((p && refs->attemptIncStrong(this)) ? p : 0)//有点看不清楚。
{
//上面那行代码够简洁,但是不方便阅读,我们写成下面这样:
/*
T* pTemp = NULL;
//关键函数attemptIncStrong
if(p != NULL && refs->attemptIncStrong(this) == true)
pTemp = p;
m_ptr = pTemp;
*/
}
2.成败在此一举
由弱生强的关键函数是attemptIncStrong,它的代码如下所示:
[-->RefBase.cpp]
bool RefBase::weakref_type::attemptIncStrong(const void* id)
{
incWeak(id); //增加弱引用计数,此时弱引用计数变为2。
weakref_impl* const impl = static_cast<weakref_impl*>(this);
int32_t curCount = impl->mStrong; //这个仍是初始值。
//下面这个循环,在多线程操作同一个对象时可能会循环多次。这里可以不去管它,
//它的目的就是使强引用计数增加1。
while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {
if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) {
break;
}
curCount = impl->mStrong;
}
if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {
bool allow;
/*
下面这个allow的判断极为精妙。impl的mBase对象就是实际对象,有可能已经被delete了。
curCount为0,表示强引用计数肯定经历了INITIAL_STRONG_VALUE->1->...->0的过程。
mFlags就是根据标志来决定是否继续进行||或&&后的判断,因为这些判断都使用了mBase,
如不做这些判断,一旦mBase指向已经回收的地址,你就等着segment fault吧!
其实,咱们大可不必理会这些东西,因为它不影响我们的分析和理解。
*/
if (curCount == INITIAL_STRONG_VALUE) {
allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK
|| impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);
} else {
allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK
&& impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);
}
if (!allow) {
//allow为false,表示不允许由弱生强,弱引用计数要减去1,这是因为咱们进来时加过一次。
decWeak(id);
return false; //由弱生强失败。
}
//允许由弱生强,强引用计数要增加1,而弱引用计数已经增加过了。
curCount = android_atomic_inc(&impl->mStrong);
if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) {
impl->mBase->onLastStrongRef(id);
}
}
impl->addWeakRef(id);
impl->addStrongRef(id);//两个函数调用没有作用。
if (curCount == INITIAL_STRONG_VALUE) {
//强引用计数变为1。
android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong);
//调用onFirstRef,通知该对象第一次被强引用。
impl->mBase->onFirstRef();
}
return true; //由弱生强成功。
}
3. 第二板斧的结果
promote完成后,相当于增加了一个强引用。根据上面所学的知识可知:
由弱生强成功后,强弱引用计数均增加1。所以现在影子对象的强引用计数为1,弱引用计数为2。
5.2.3 第三板斧—破解生死魔咒
1. 延长生命的魔咒
RefBase为我们提供了一个这样的函数:
extendObjectLifetime(int32_t mode)
另外还定义了一个枚举:
enum {
OBJECT_LIFETIME_WEAK = 0x0001,
OBJECT_LIFETIME_FOREVER = 0x0003
};
注意:FOREVER的值是3,用二进制表示是B11,而WEAK的二进制是B01,也就是说FOREVER包括了WEAK的情况。
上面这两个枚举值,是破除强弱引用计数作用的魔咒。先观察flags为OBJECT_LIFETIME_ WEAK的情况,见下面的例子。
[-->例子3]
class A:public RefBase
{
public A()
{
extendObjectLifetime(OBJECT_LIFETIME_WEAK);//在构造函数中调用。
}
}
int main()
{
A *pA = new A();
wp<A> wpA(pA);//弱引用计数加1。
{
sp<A> spA(pA) //sp后,结果是强引用计数为1,弱引用计数为2。
}
....
}
sp的析构将直接调用RefBase的decStrong,它的代码如下所示:
[-->RefBase.cpp]
void RefBase::decStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
refs->removeStrongRef(id);
const int32_t c = android_atomic_dec(&refs->mStrong);
if (c == 1) { //上面进行原子操作后,强引用计数为0
const_cast<RefBase*>(this)->onLastStrongRef(id);
//注意这句话。如果flags不是WEAK或FOREVER的话,将delete 数据对象。
//现在我们的flags是WEAK,所以不会delete 它。
if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
delete this;
}
}
refs->removeWeakRef(id);
refs->decWeak(id);//调用前弱引用计数是2。
}
然后调用影子对象的decWeak。再来看它的处理,代码如下所示:
[-->RefBase.cpp::weakref_type的decWeak()函数]
void RefBase::weakref_type::decWeak(const void* id)
{
weakref_impl* const impl = static_cast<weakref_impl*>(this);
impl->removeWeakRef(id);
const int32_t c = android_atomic_dec(&impl->mWeak);
if (c != 1) return; //c为2,弱引用计数为1,直接返回。
/*
假设我们现在到了例子中的wp析构之处,这时也会调用decWeak,在调用上面的原子减操作后
c=1,弱引用计数变为0,此时会继续往下运行。由于mFlags为WEAK ,所以不满足if的条件。
*/
if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
if (impl->mStrong == INITIAL_STRONG_VALUE)
delete impl->mBase;
else {
delete impl;
}
} else {//flag为WEAK,满足else分支的条件。
impl->mBase->onLastWeakRef(id);
/*
由于 flags值满足下面这个条件,所以实际对象会被delete,根据前面的分析可知,实际对象的delete会检查影子对象的弱引用计数,如果它为0,则会把影子对象也delete掉。
由于影子对象的弱引用计数此时已经为0,所以影子对象也会被delete。
*/
if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {
delete impl->mBase;
}
}
}
2. LIFETIME_WEAK的魔力
看完上面的例子,我们发现什么了?
在LIFETIME_WEAK的魔法下,强引用计数为0,而弱引用计数不为0的时候,实际对象没有被delete!只有当强引用计数和弱引用计数同时为0时,实际对象和影子对象才会被delete。
3. 魔咒大揭秘
至于LIFETIME_FOREVER的破解,就不用再来一斧子了,我直接给出答案:
flags为0,强引用计数控制实际对象的生命周期,弱引用计数控制影子对象的生命周期。强引用计数为0后,实际对象被delete。所以对于这种情况,应记住的是,使用wp时要由弱生强,以免收到segment fault信号。
flags为LIFETIME_WEAK,强引用计数为0,弱引用计数不为0时,实际对象不会被delete。当弱引用计数减为0时,实际对象和影子对象会同时被delete。这是功德圆满的情况。
flags为LIFETIME_FOREVER,对象将长生不老,彻底摆脱强弱引用计数的控制。所以你要在适当的时候杀死这些“老妖精”,免得她祸害“人间”。
5.2.4 轻量级的引用计数控制类LightRefBase
上面介绍的RefBase,是一个重量级的引用计数控制类。那么,究竟有没有一个简单些的引用计数控制类呢?Android为我们提供了一个轻量级的LightRefBase。这个类非常简单,我们不妨一起来看看。
[-->RefBase.h]
template <class T>
class LightRefBase
{
public:
inline LightRefBase() : mCount(0) { }
inline void incStrong(const void* id) const {
//LightRefBase只有一个引用计数控制量mCount。incStrong的时候使它增加1。
android_atomic_inc(&mCount);
}
inline void decStrong(const void* id) const {
//decStrong的时候减1,当引用计数变为零的时候,delete掉自己。
if (android_atomic_dec(&mCount) == 1) {
delete static_cast<const T*>(this);
}
}
inline int32_t getStrongCount() const {
return mCount;
}
protected:
inline ~LightRefBase() { }
private:
mutable volatile int32_t mCount;//引用计数控制变量。
};
LightRefBase类够简单吧?不过它是一个模板类,我们该怎么用它呢?下面给出一个例子,其中类A是从LightRefBase派生的,写法如下:
class A:public LightRefBase<A> //注意派生的时候要指明是LightRefBase<A>。
{
public:
A(){};
~A(){};
};
另外,我们从LightRefBase的定义中可以知道,它支持sp的控制,因为它只有incStrong和decStrong函数。
5.2.5 题外话—三板斧的来历
从代码量上看,RefBase、sp和wp的代码量并不多,但里面的关系,尤其是flags的引入,曾一度让我眼花缭乱。当时,我确实很希望能自己调试一下这些例子,但在设备上调试native代码,需要花费很大的精力,即使是通过输出log的方式来调试也需要花很多时间。该怎么解决这一难题?
既然它的代码不多而且简单,那何不把它移植到台式机的开发环境下,整一个类似的RefBase呢?有了这样的构想,我便用上了Visual Studio。至于那些原子操作,Windows平台上有很直接的InterlockedExchangeXXX与之对应,真的是踏破铁鞋无觅处,得来全不费功夫!(在Linux平台上,不考虑多线程的话,将原子操作换成普通的非原子操作不是也可以吗?如果更细心更负责任的话,你可以自己用汇编来实现常用的原子操作,内核代码中有现成的函数,一看就会明白。)
如果把破解代码看成是攻城略地的话,我们必须学会灵活多变,而且应力求破解方法日臻极致!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)