c++11 之智能指针

news2024/11/26 4:51:49

文章目录

      • std::shared_ptr
      • std::weak_ptr
      • std::unique_ptr
      • 智能指针多线程安全问题

在实际的 c++ 开发中,我们经常会遇到诸如程序运行中突然崩溃、程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的。比如:

  • 有些内存资源已经被释放,但指向它的指针并没有改变指向(成为了野指针),并且后续还在使用;
  • 有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行崩溃);
  • 没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多。

针对以上这些情况,很多程序员认为 c++ 应该提供更友好的内存管理机制,这样就可以将精力集中于开发项目的各个功能上。事实上,早在 1959 年前后,就有人提出了“垃圾自动回收”机制。所谓垃圾,指的是那些不再使用或者没有任何指针指向的内存空间,而“回收”则指的是将这些“垃圾”收集起来以便再次利用。

如今,垃圾回收机制已经大行其道,得到了诸多编程语言的支持,例如 Java、Python、C#、PHP 等。而 c++ 虽然从来没有公开得支持过垃圾回收机制,但 c++98/03 标准中,支持使用 auto_ptr 智能指针来实现堆内存的自动回收;c++11 新标准在废弃 auto_ptr 的同时,增添了 unique_ptr、shared_ptr 以及 weak_ptr 这 3 个智能指针来实现堆内存的自动回收。

实际上,每种智能指针都是以类模板的方式实现的,例如 shared_ptr<T>(其中 T 表示指针指向的具体数据类型)的定义位于 <memory> 头文件,
并位于 std 命名空间中。所以使用智能指针,需要导入头文件 <memory> 并显示标明 std 命名空间

智能指针对象本身是存放在栈区空间,而智能指针指向的对象是存放在堆区空间,比如:

shared_ptr<Buffer> buf = make_shared<Buffer>("auto free memory"); }

// buf 是在栈上
// make_shared<Buffer>("auto free memory"); }  是在堆上分配的

智能指针使用场景

  • 使用智能指针可以自动释放占用的内存
    shared_ptr<Buffer> buf = make_shared<Buffer>("auto free memory");  // Buffer对象分配在堆上,但能自动释放
    Buffer *buf = new Buffer("auto free memory");  // Buffer对象分配在堆上,但需要手动 delete释放
    
  • 共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题

std::shared_ptr

shared_ptr 共享智能指针特点

  • 多个 shared_ptr 智能指针可以共同使用同一块堆内存。
  • 由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针,只有引用计数为 0 时,堆内存才会被自动释放。
    也就是说 shared_ptr 共享对象的所有权

shared_ptr 共享智能指针的内存模型如下:

简单来说,shared_ptr 实现包含了两部分(包含两个指针):

  • 一个指向对象(T Object),即指向堆上创建的对象的裸指针(raw_ptr);
  • 一个指向控制块(control block),控制块中包含一个引用计数(reference count), 一个弱计数(weak count)和其它一些数据。即指向内部隐藏的、共享的管理对象(share_count_object)。

shared_ptr 共享智能指针的创建
(1)通过如下 2 种方式,可以构造出 shared_ptr<T> 类型的空智能指针(注意,空的 shared_ptr 指针,其初始引用计数为 0,而不是 1):

std::shared_ptr<int> p1;             //不传入任何实参
std::shared_ptr<int> p2(nullptr);    //传入空指针 nullptr

(2)在构建 shared_ptr 智能指针,也可以明确其指向,同样有两种方式。例如:

// 其指向一块存有 10 这个 int 类型数据的堆内存空间。
std::shared_ptr<int> p3(new int(10));

// 调用std::make_shared<T> 模板函数:c++11 中提供了 std::make_shared<T> ,其可以用于初始化 shared_ptr 智能指针
std::shared_ptr<int> p3 = std::make_shared<int>(10);

我们应该优先使用 make_shared 来构造智能指针,因为他更高效
(3)除此之外,shared_ptr<T> 模板还提供有相应的拷贝构造函数和移动构造函数,例如:

//调用拷贝构造函数
std::shared_ptr<int> p4(p3);
//或者 std::shared_ptr<int> p4 = p3;

//调用移动构造函数
std::shared_ptr<int> p5(std::move(p4)); 
//或者 std::shared_ptr<int> p5 = std::move(p4);

(4)默认情况下,shared_ptr 指针采用 std::default_delete<T> 方法释放堆内存。当然,在初始化 shared_ptr 智能指针时,也可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为 0 时,会优先调用我们自定义的释放规则。

//指定 default_delete 作为释放规则
std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());  

//自定义释放规则
void deleteInt(int*p) {
	delete []p;
}
//初始化智能指针,并自定义释放规则
std::shared_ptr<int> p7(new int[10], deleteInt);

// 删除器可以是一个lambda表达式,上面的写法可以改为
std::shared_ptr<int> p8(new int(1), [](int *p) { 
	cout << "call lambda delete p" << endl; 
	delete p;});

当我们用 shared_ptr 管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数组对象,代码如下所示:

std::shared_ptr<int> p(new int[10], [](int *p) { delete [] p;});

shared_ptr 共享智能指针的成员方法

【注意】

  • p.get() 函数:返回 shared_ptr 中保存的裸指针,即指向堆上创建的对象的裸指针;
  • p.reset() 函数:重置shared_ptr;
    • 当智能指针调用了 p.reset() 函数的时候,就不会再指向这个对象了。
      • 如果没有参数,智能指针会置为空;
      • 如果有参数,智能指针会指向新对象(参数);
    • 如果还有其它智能指针指向这个对象,那么其他的智能指针的引用计数会减1。如果没有其它智能指针指向这个对象,这个对象将会被释放。
  • p.use_count() 函数:返回 shared_ptr 的强引用计数;
  • p.unique() 函数:若 p.use_count() 为1,返回true,否则返回false。
#include <iostream>
#include <memory>

int main() {
    //构建 2 个智能指针
    std::shared_ptr<int> p1(new int(10));
    std::shared_ptr<int> p2(p1);
    //输出 p2 指向的数据
    std::cout << *p2 << std::endl;
    p1.reset();//引用计数减 1,p1为空指针
    if (p1) {   // 智能指针可以通过重载的 bool 类型操作符来判断
        std::cout << "p1 不为空" << std::endl;
    }
    else {
        std::cout << "p1 为空" << std::endl;
    }
    //以上操作,并不会影响 p2
    std::cout << *p2 << std::endl;
    //判断当前和 p2 同指向的智能指针有多少个
    std::cout << p2.use_count() << std::endl;
    return 0;
}
// 执行结果:
// 10  
// p1 为空  
// 10  
// 1

关于 shared_ptr 共享智能指针有几点需要注意:

  • 对于 p.get() 函数获取裸指针,谨慎使用其返回值,如果不知道其危险性则永远不要调用 p.get()函数,因为:
    • 保存 p.get() 的返回值 ,无论是保存为裸指针还是 shared_ptr 都是错误的:保存为裸指针不知什么时候就会变成空悬指针,保存为 shared_ptr 则产生了独立指针;
    • 不要 delete p.get() 的返回值 ,会导致对一块内存 delete 两次的错误
      std::shared_ptr<int> ptr(new int(1));
      int *p = ptr.get();
      delete p;  // 一不小心
      
  • 同一裸指针不能同时为多个 shared_ptr 对象赋值,否则会导致程序发生异常而崩溃:
    int* ptr = new int;
    std::shared_ptr<int> p1(ptr);
    std::shared_ptr<int> p2(ptr);   //错误
    
  • 不要在函数实参中创建 shared_ptr,因为 c++ 的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,正确的写法应该是先创建智能指针:
    // 错误方式:
    function(shared_ptr<int>(new int), g()); 
    
    // 正确方式:
    shared_ptr<int> p(new int);
    function(p, g());
    
  • 通过 shared_from_this() 可以返回 this 指针,不要把 this 指针作为 shared_ptr 返回出来,因为 this 指针本质就是裸指针,通过 this 返回可能会导致重复析构,不能把 this 指针交给智能指针管理。
    // 先继承 std::enable_shared_from_this<T> 类,再使用 shared_from_this()
    class A :std::enable_shared_from_this<A> // 注意:继承
    {
    	shared_ptr<A> GetSelf() {
    		return shared_from_this();     // 正确
    		// return shared_ptr<A>(this); 错误,会导致double free
    	}
    }
    
  • 尽量使用 make_shared,少用 new。
  • 不是 new 出来的空间要自定义删除器。
  • 不能将一个原始指针直接赋值给一个智能指针,shared_ptr 不能通过“直接将原始这种赋值”来初始化,需要通过构造函数和辅助方法来初始化。例如,下面这种方法是错误的:
    std::shared_ptr<int> p = new int(1);
    
  • 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。

std::weak_ptr

为什么需要 weak_ptr
share_ptr 虽然已经很好用了,但是有一点 share_ptr 智能指针还是有内存泄露的情况,当两个对象相互使用一个 shared_ptr 成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏,如下所示:

using namespace std;
struct A;
struct B;
struct A {
    std::shared_ptr<B> bptr;
    ~A() { cout << "A delete" << endl; }
};

struct B {
    std::shared_ptr<A> aptr;
    ~B() { cout << "B delete" << endl; }
};

int main() {
    auto aaptr = std::make_shared<A>();
    auto bbptr = std::make_shared<B>();
    aaptr->bptr = bbptr;
    bbptr->aptr = aaptr;
    return 0;
}

上面代码,产生了循环引用,导致 aptr 和 bptr 的引用计数为 2,离开作用域后 aptr 和 bptr 的引用计数 1,但是永远不会为 0,导致指针永远不会析构,产生了内存泄漏,如何解决这种问题呢,答案是使用 weak_ptr。

weak_ptr 特点
c++11 标准虽然将 weak_ptr 定位为智能指针的一种,但该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用。甚至于,我们可以将 weak_ptr 类型指针视为 shared_ptr 指针的一种辅助工具,借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。

当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数,weak_ptr 是用来监视 shared_ptr 的生命周期,它不管理 shared_ptr 内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视 shared_ptr 中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。

除此之外,weak_ptr<T> 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。

struct A;
struct B;
struct A {
    std::shared_ptr<B> bptr;
    ~A() { cout << "A delete" << endl; }
    void Print() { cout << "A" << endl; }
};

struct B {
    std::weak_ptr<A> aptr;  // 这里改成weak_ptr
    ~B() { cout << "B delete" << endl; }
    void PrintA() {
        if (!aptr.expired()) {  // 监视shared_ptr的生命周期
            auto ptr = aptr.lock();
            ptr->Print();
        }
    }
};

int main() {
    auto aaptr = std::make_shared<A>();
    auto bbptr = std::make_shared<B>();
    aaptr->bptr = bbptr;
    bbptr->aptr = aaptr;
    bbptr->PrintA();
    return 0;
}
// 输出:
// A
// A delete 
// B delete

weak_ptr 创建
1、可以创建一个空 weak_ptr 指针,例如:

std::weak_ptr<int> wp1;

2、凭借已有的 weak_ptr 指针,可以创建一个新的 weak_ptr 指针,例如:

std::weak_ptr<int> wp2 (wp1);

若 wp1 为空指针,则 wp2 也为空指针;反之,如果 wp1 指向某一 shared_ptr 指针拥有的堆内存,则 wp2 也指向该块存储空间(可以访问,但无所有权)。
3、weak_ptr 指针更常用于指向某一 shared_ptr 指针拥有的堆内存,因为在构建 weak_ptr 指针对象时,可以利用已有的 shared_ptr 指针为其初始化。例如:

std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp3 (sp);

weak_ptr 模板类提供的成员方法

【注意】

  • 通过 use_count() 方法获取当前观察资源的引用计数。
  • 通过 expired() 方法判断所观察资源是否已经释放。
  • 通过 lock() 方法获取监视的 shared_ptr。在实际使用中,一定是先调用 lock() 函数锁好资源,再调用 expired() 函数判断资源是否存在 shared_ptr。因为我们需要先通过 lock 锁住资源,那么此时可能存在两种情况,一种是资源存在,那么上锁成功,,此时通过 expired 判断资源存在就可以去使用;一种是资源在上锁前已经被释放或者被其他地方锁住,此时通过 expired 判断资源不存在。
       std::weak_ptr<int> gw;
       auto spt = gw.lock();  // 锁好资源再去判断是否有
       std::this_thread::sleep_for(std::chrono::seconds(2));
       if (gw.expired()) {
    	   cout << "gw Invalid, resource released\n";
    	} else {
    		cout << "gw Valid, *spt = " << *spt << endl;
    	}
    
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp1(new int(10));
    std::shared_ptr<int> sp2(sp1);
    std::weak_ptr<int> wp(sp2);
    //输出和 wp 同指向的 shared_ptr 类型指针的数量
    std::cout << wp.use_count() << std::endl;
    //释放 sp2
    sp2.reset();
    std::cout << wp.use_count() << std::endl;
    //借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据
    std::cout << *(wp.lock()) << std::endl;
    return 0;
}
// 程序执行结果为:
// 2  
// 1  
// 10

关于 weak_ptr 智能指针有几点需要注意:
(1)weak_ptr 返回 this 指针
shared_ptr 章节中提到不能直接将 this 指针返回 shared_ptr,需要通过派生 std::enable_shared_from_this 类,并通过其方法 shared_from_this 来返回指针,原因是 std::enable_shared_from_this类中有一个 weak_ptr,这个 weak_ptr 用来观察 this 智能指针,调用 shared_from_this() 方法是会调用内部这个 weak_ptr 的 lock() 方法,将所观察的 shared_ptr 返回,再看前面的范例

(2)weak_ptr在使用前需要检查合法性。

weak_ptr<int> wp;
{
    shared_ptr<int> sp(new int(1));  // sp.use_count()==1
    wp = sp;                         // wp不会改变引用计数,所以sp.use_count()==1
    shared_ptr<int> sp_ok = wp.lock();  // wp没有重载->操作符。只能这样取所指向的对象
}
shared_ptr<int> sp_null = wp.lock();  // sp_null .use_count()==0;

上述代码中 sp 和 sp_ok 离开了作用域,其容纳的对象已经被释放了。得到了一个容纳 NULL 指针的 sp_null 对象。在使用 wp 前需要调用 wp.expired() 函数判断一下。因为 wp 还仍旧存在,虽然引用计数等于 0,仍有某处“全局”性的存储块保存着这个计数信息。直到最后一个 weak_ptr 对象被析构,这块“堆”存储块才能被回收。否则 weak_ptr 无法直到自己所容纳的那个指针资源的当前状态。

std::unique_ptr

unique_ptr 独占智能指针的特点
和 shared_ptr 指针最大的不同之处在于,unique_ptr 指针指向的堆内存无法同其它 unique_ptr 共享,也就是说,每个 unique_ptr 指针都独自拥有对其所指堆内存空间的所有权,不允许通过赋值将一个 unique_ptr 赋值给另一个 unique_ptr。

unique_ptr 独占智能指针的创建
1、通过以下 2 种方式,可以创建出空的 unique_ptr 指针:

std::unique_ptr<int> p1();
std::unique_ptr<int> p2(nullptr);

2、创建 unique_ptr 指针的同时,也可以明确其指向。例如:

std::unique_ptr<int> p3(new int);

和可以用 make_shared<T>() 模板函数初始化 shared_ptr 指针不同,c++11 标准中并没有为 unique_ptr 类型指针添加类似的模板函数。到 c++14 中才出现 make_unique<T>() 模板函数用于初始化 unique_ptr。

auto upw1(std::make_unique<Widget>()); // with make func
std::unique_ptr<Widget> upw2(new Widget); // without make func

3、基于 unique_ptr 类型指针不共享各自拥有的堆内存,因此 c++11 标准中的 unique_ptr 模板类没有提供拷贝构造函数,unique_ptr不允许复制,只提供了移动构造函数。例如:

std::unique_ptr<int> p4(new int);
std::unique_ptr<int> p5(p4);//错误,堆内存不共享
std::unique_ptr<int> p5(std::move(p4));//正确,调用移动构造函数
unique_ptr<int> p6 = p4; // 报错,不能复制

对于调用移动构造函数的 p4 和 p5 来说,p5 将获取 p4 所指堆空间的所有权,而 p4 将变成空指针(nullptr)。

4、默认情况下,unique_ptr 指针采用 std::default_delete<T> 方法释放堆内存。当然,我们也可以自定义符合实际场景的释放规则。值得一提的是,和 shared_ptr 指针不同,为 unique_ptr 自定义释放规则,只能采用函数对象的方式。例如:

//自定义的释放规则
struct myDel {
    void operator()(int *p) {
        delete p;
    }
};
std::unique_ptr<int, myDel> p6(new int);
//std::unique_ptr<int, myDel> p6(new int, myDel());

【注意】shared_ptr 与 unique_ptr 不能够混合使用

unique_ptr<A> my_ptr(new A);
shared_ptr<A> my_other_ptr = my_ptr; // 报错

unique_ptr 模板类提供的成员方法

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> p5(new int);
    *p5 = 10;
    // p 接收 p5 释放的堆内存
    int *p = p5.release();
    std::cout << *p << std::endl;
    //判断 p5 是否为空指针
    if (p5) {
        std::cout << "p5 is not nullptr" << std::endl;
    } else {
        std::cout << "p5 is nullptr" << std::endl;
    }
    std::unique_ptr<int> p6;

    // p6 获取 p 的所有权
    p6.reset(p);
    std::cout << *p6 << std::endl;
    return 0;
}
// 运行结果
// 10  
// p5 is nullptr  
// 10

unique_ptr 独占智能指针与 share_ptr 共享智能指针的区别
除了 unique_ptr 的独占性, unique_ptr 和 shared_ptr 还有一些区别:
1、unique_ptr 可以指向一个数组,代码如下所示

std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
std::shared_ptr<int []> ptr2(new int[10]); // 这个是不合法的

2、unique_ptr 指定删除器和 shared_ptr 有区别

std::shared_ptr<int> ptr3(new int(1), [](int *p){delete p;}); // 正确

std::unique_ptr<int> ptr4(new int(1), [](int *p){delete p;}); // 错误
std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;}); // 正确

unique_ptr 需要确定删除器的类型,所以不能像 shared_ptr 那样直接指定删除器,只能采用函数对象的方式。

3、使用场景区别
关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。
如果希望只有一个智能指针管理资源或者管理数组就用 unique_ptr,如果希望多个智能指针管理同一个资源就用 shared_ptr。

智能指针多线程安全问题

智能指针的引用计数本身是安全的,因为引用计数采用原子(atomic)计数,原子计数是线程安全的;至于智能指针是否安全需要结合实际使用分情况讨论:
情况1:多线程代码操作的是同一个 shared_ptr 的对象,此时是不安全的。比如 std::thread 的回调函数,是一个lambda表达式,其中引用捕获了一个 shared_ptr:std::thread td([&sp1]()){....});;又或者通过回调函数的参数传入的shared_ptr对象,参数类型引用:

void fn(shared_ptr<A>&sp) {
...
}
..
std::thread td(fn, sp1);

情况2:多线程代码操作的不是同一个 shared_ptr 的对象。这里指的是管理的数据是同一份,而 shared_ptr 不是同一个对象。比如多线程回调的 lambda 的是按值捕获的对象:std::thread td([sp1]()){....});;另个线程传递的shared_ptr是值传递,而非引用:

void fn(shared_ptr<A>sp) {
	...
}
..
std::thread td(fn, sp1);

这时候每个线程内看到的 sp,他们所管理的是同一份数据,用的是同一个引用计数。但是各自是不同的对象,当发生多线程中修改 sp 指向的操作的时候,是不会出现非预期的异常行为的。也就是说,如下操作是安全的:

void fn(shared_ptr<A> sp) {
    ...
    if (..) {
        sp = other_sp;
    } else {
        sp = other_sp2;
    }
}

【注意】所管理数据的线程安全性问题。显而易见,所管理的对象必然不是线程安全的,必然 sp1、sp2、sp3 智能指针实际都是指向对象A, 三个线程同时操作对象 A,那对象的数据安全必然是需要对象 A 自己去保证。

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

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

相关文章

浅谈Synchronized的原理

文章目录1.引言2.Synchronized使用方式2.1.普通函数2.2.静态函数2.3.代码块3.Synchronized原理4.Synchronized优化4.1.锁粗化4.2.锁消除4.3.锁升级4.4.无锁4.5.锁偏向锁4.6.轻量级锁4.7.重量级锁5.整个锁升级的过程1.引言 在并发编程中Synchronized一直都是元老级的角色&#…

斗地主洗牌发牌-课后程序(JAVA基础案例教程-黑马程序员编著-第六章-课后作业)

【案例6-4】 斗地主洗牌发牌 【案例介绍】 1.任务描述 扑克牌游戏“斗地主”&#xff0c;相信许多人都会玩&#xff0c;本案例要求编写一个斗地主的洗牌发牌程序&#xff0c;要求按照斗地主的规则完成洗牌发牌的过程。一副扑克总共有54张牌&#xff0c;牌面由花色和数字组成…

Linux 定时任务调度(crontab)

一、Crontab Crontab命令用于设置周期性被执行的指令。该命令从标准输入设备读取指令&#xff0c;并将其存放于“crontab”文件中&#xff0c;以供之后读取和执行。 可以使用Crontab定时处理离线任务&#xff0c;比如每天凌晨2点更新数据等&#xff0c;经常用于系统任务调度。…

【Linux】冯.诺依曼体系结构与操作系统

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;冯.诺依曼体系结构什么是冯诺依曼体系结构&#xff1f;我们如今的计算机比如笔记本&#xff0c;或者是服务器&#xff0c;基本上都遵循冯诺依曼体系结构…

记一次web漏洞挖掘随笔

最近挖了一些漏洞。虽然重复了&#xff0c;但是有参考价值。这边给大家分享下。漏洞重复还是很难受的&#xff0c;转念一想&#xff0c;人生从不是事事如人意的&#xff0c;漏洞重复忽略&#xff0c;不代表失败。先来后到很重要&#xff0c;出场顺序很重要。1.某站rce 忽略理由…

Docker----------DockerFile解析

1. 是什么 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 官网&#xff1a;https://docs.docker.com/engine/reference/builder/ 1.编写Dockerfile文件 2.docker build命令构建镜像 3.docker run依镜像运行容器实例…

第47天|LeetCode392. 判断子序列、LeetCode392. 判断子序列

1.题目链接&#xff1a;392. 判断子序列 题目描述&#xff1a; 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&…

Barra模型因子的构建及应用系列四之Residual Volatility因子

一、摘要 在前期的Barra模型系列文章中&#xff0c;我们构建了Size因子、Beta因子和Momentum因子&#xff0c;并分别创建了对应的单因子策略。本节文章将在该系列下进一步构建Residual Volatility因子&#xff0c;该策略在2022年以来跑赢大盘指数&#xff0c;且具有波动小的特…

Linux内核内存管理

目录 一、内核内存管理框架 二、内核中常用动态分配 2.1 kmalloc 2.2 vmalloc 2.3 kmalloc & vmalloc 的比较 2.4 分配选择原则&#xff1a; 三、IO访问-------访问外设控制器的寄存器 四、led驱动 1. 读原理图 2. 查阅SOC芯片手册 3. 编写驱动 一、内核内存管理…

Leetcode之消失的数字轮转数组

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录一、消失的数字一、消失的数字 二、旋转数组 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、消失的数字 这题找出消失的一个数字&#…

自行车出口欧盟CE认证,新版自行车标准ISO 4210:2023与ISO 8098:2023发布

2023年1月&#xff0c;国际标准化组织ISO发布了新版“自行车以及儿童自行车的测试标准”&#xff0c;即ISO 4210&#xff1a;2023以及ISO 8098:2023&#xff0c;用于取代了SO 4210&#xff1a;2015以及ISO 8098:2015。新版标准一经发布&#xff0c;立即生效。欧盟标准化委员会C…

使用 Python 抓取和优化所有网站图像

&#xff0c;我发布了一个通过FTP自动优化新图像的教程。这次我们将抓取整个网站&#xff0c;并在本地优化我们遇到的图像&#xff0c;按URL组织。请注意&#xff0c;这个简短但中级的脚本不适用于大型站点。首先&#xff0c;所有图像都转储到一个文件夹中。为每个页面创建一个…

ASP.NET 网站开发(联合增,删,改,查)

联合多表查询查询&#xff1a; linqDBDataContext db new linqDBDataContext(); stu d db.stu.Where(p > p.sid 2).FirstOrDefault(); if (d ! null) { var Marks d.marks; GridView1.DataSource Marks; GridView1.DataBind(); db.Su…

理解B树及B+树、B*树

目录 一.B树概念 二.B树插入思路 二.B树分部实现 1.树节点类 2.B树成员结构 3.查找函数 4.插入函数&#xff08;核心&#xff09; 5.插入关键值 6.中序遍历&#xff08;有序&#xff09; 三.B树实现总代码 四.B树性能分析 五.B树和B*树 1.B树 2.B*树 3.总结 六…

Android上架构建KeyStore应用签名

Android上架构建KeyStore应用签名Android上架构建KeyStore应用签名构建签名生成密钥读取填写使用Android上架构建KeyStore应用签名 我们的应用在上架前需要构建应用签名&#xff0c;使用的是Java进行构建 构建签名 需要有Java环境&#xff0c;注意尽量使用JDK8&#xff01;使…

CIMCAI super unmanned intelligent gate container damage detect

世界港航人工智能领军者企业CIMCAI中集飞瞳打造全球最先进超级智能闸口无人闸口ceaspectusG™视频流动态感知集装箱箱况残损检测箱况残损识别率99%以上&#xff0c;箱信息识别率99.95%以上World port shipping AI leader CIMCAIThe worlds most advanced super intelligent gat…

RK3588 PMIC/Power电路 PCB 设计指南

1、VDD_LOGIC&#xff0c;VDD_GPU&#xff0c;VDD_NPU&#xff0c;VDD_CPU电源的 DC-DC 远端反馈设计。100ohm反馈电阻需要靠近输出电容放置&#xff0c;电阻一端连接到 DC-DC 输出电容&#xff0c;另一端连接到PMIC 的VOUT 反馈脚上&#xff0c;并同时连接到 RK3588 电源管脚同…

网络信息安全(四)

IIS WEB服务器 服务器配置静态IP 安装WEB服务软件 打开软件 检查80端口是否打开 DNS解析不同域名站点 新建两个网页京东和淘宝 安装DNS组件并创建两个区域 新建主机 XP上指定DNS 正常情况下同一个服务器上一个端口只提供一个服务 添加主机头值 XP验证 IIS FTP服务器 FTP工作模式…

【20230221】【剑指1】排序(中等)II

1.最小的K个数sort直接用太无脑了&#xff0c;虽然底层实现也是快排&#xff0c;但是快排还是得会写啊快速排序快速排序算法有两个核心点&#xff0c;分别为 “哨兵划分” 和 “递归” 。哨兵划分操作&#xff1a; 以数组某个元素&#xff08;一般选取首元素&#xff09;为 基准…

教你编写SQLMap的Tamper脚本过狗

测试环境 最新版某狗 测试方法 安全狗其实是比较好绕的WAF&#xff0c;绕过方法很多&#xff0c;但这里我们就用一种&#xff1a;注释混淆 一招鲜吃遍天 注释混淆&#xff0c;其实就是在敏感位置添加垃圾字符注释&#xff0c;常用的垃圾字符有/、!、*、%等 这里再解释一下…