C/C++内存管理 | new的机制 | 重载自己的operator new

news2024/11/14 10:23:17

一、C/C++内存分布

1. 内存分区

image-20220911121632192

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信 .
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的
  4. 数据段–存储全局数据和静态数据 (也叫静态区)
  5. 代码段–可执行的代码/只读常量 (也叫常量区)

2. 程序运行时加载过程

首先,我们平时所写的代码(如test.cpp)存放在那?

其实是存放在磁盘上的,因为是文件形式

而运行一个程序的过程是怎么样的?

写好的代码 -> 编译链接 -> 可执行程序

也就说,我们写好代码点击运行就是运行的这个可执行程序

可执行程序(Windows下.exe、Linux下a.out)中包括:

  1. 二进制指令代码(CPU读取)
  2. 数据
  3. 其他一些内容

而当程序运行时首先要加载哪些东西到内存呢?

  1. 二进制指令代码 -> 代码段
  2. 常量数据 -> 代码段
  3. 全局变量 -> 数据段 (因为全局变量在main函数前已经定义好)

堆栈上的数据何时创建?

  1. 当二进制指令代码加载到代码段之后,由CPU依次读取并执行二进制指令

  2. 当开始执行main函数时,开始创建函数栈帧,此时栈中开辟的变量和数据就开始定义了。

  3. 而堆上的数据也是在栈上,通过malloc等动态开辟内存的函数来开辟的

注意

类、函数、符号表、公共代码区等概念都是在编译链接阶段的概念

编译链接之后,类、函数等都转变成了二进制指令加载到代码了,

不存在所谓的类或者函数了

3. 常见数据类型在内存中的分布

image-20220911122632879

二、动态内存管理

C/C++中,除了堆以外的内存分区中资源的申请与释放不用我们管,系统会自动处理。(所以内存泄漏等问题出现在堆上)

1. C语言中动态内存管理方式

malloc : 申请一块空间

calloc :申请一块空间并初始化

realloc :对一段空间进行扩容

使用完都需要 free,防止内存泄漏

void Test ()
{
	int* p1 = (int*) malloc(sizeof(int));
	free(p1);
// 1.malloc/calloc/realloc的区别是什么?
	int* p2 = (int*)calloc(4, sizeof (int));
	int* p3 = (int*)realloc(p2, sizeof(int)*10);
    
// 这里需要free(p2)吗? 
// 注意: 不需要
    // 因为如果原地扩容:释放p3就相当于释放了p2
    // 如果异地扩容,realloc会自动完成原始空间的free
	free(p3 );
}

2. C++动态内存管理

虽然C++向下兼容C,但是有些地方C的方式是无能为力的,所以C++又搞了一套自己的动态内存管理方式

即:new 和 delete操作符

注意:new和delete 不是函数,是操作符
new一个空间,使用delete释放
new多个空间,使用delete [] 释放

① new和delete针对 内置类型

//    C语言动态开辟:
int* p1 = (int*)malloc(sizeof(int));
if(p1==NULL)
{
    perror("malloc fail");
    exit(-1);
}

//    C++动态开辟

//申请一个int类型大小的空间 
int* p2 = new int;//new自己去算int是多少字节
 
//开辟多个int的空间 -- 也符合后定义的先析构!
int* p3 = new int[5];//开5个int大小的空间

//申请一个int类型大小的空间,并初始化
int* p4 = new int(5); //申请一个int大小的空间,并初始化为5

//对new的数组初始化 :{} (C++98不支持,C++11才支持)
int* p5 = new int[5]{ 1,2,3,}; //初始化为 1 2 3 0 0 (不给的默认给0)


//  释放

free(p1);//C的释放方式

delete p2; // 针对new int
delete p4; // 和 new int()
delete[] p3; //针对new int[] 

注意

new/delete 和 new[] /delete[] 一定要匹配,否则有时候会出现崩溃。一般来说自定义类型一定会报错,自定义类型一般不会报错

结论

1. 针对内置类型,new/delete 和 malloc/free没有本质的区别,只有用法上的区别,new/delete只是用法简化了

2. malloc的需要去检查是否开辟成功,new不需要,如果失败默认会抛异常

② new和delete针对自定义类型

new和delete针对内置类型与C的malloc/free无大区别,但是对于自定义类型,区别很大!
这也是引入new/delete的原因

new/delete针对自定义类型,与malloc/free最大的区别

  1. 就是new的时候会自动调用 默认构造函数(如果无默认构造会报错)
  2. delete的时候会自动调用析构函数

1. 单个自定义类型对象的构造/析构

//C语言用malloc,malloc不会初始化
A* p1 = (A*)malloc(sizeof(A));
if (p1 == nullptr)
{
		perror("malloc fail");
		exit(-1);
}

//  C++使用new,有两步:
//  1. new申请空间 2. 调用默认构造函数初始化
A* p2 = new A;
A* p3 = new A(10);// 显示传递参数来构造


//释放空间的区别:
//C语言的free只是释放该对象,不会清理对象中的资源
free(p1);

//C++: 1.调用析构函数清理对象中的资源 2. 释放空间
delete p2;
delete p3;

多个对象初始化

  1. 多个对象默认构造初始化(前提要提供默认构造函数)
A* p4 = new A[10];//默认构造(10次)
delete[] p4;
  1. 多个对象显示构造初始化

    /* 写法比较多 */
    
    A* p5 = new A[10]{ 1,2,3,4,5 };//开10个,前5个显示调用构造初始化,后5个默认构造
    
    A* p6 = new A[]{ 1,2,3,4,5 };//后面有几个数组就开几个对象的大小
    
    A* p7 = new A[]{ (1),(2) };//用括号
    A* p8 = new A[]{ {1},{2} };//用花括号
    
    A* p9 = new A[]{ A(1),A(2)};//用匿名构造
    

    最常用的是第一种和第二种

    注意:支持C++11的编译器才可以显示构造初始化

    VS2013就不支持,2019以上是支持的

结论

new/delete是为自定义类型准备的,不仅在堆上申请出来,还会调用构造和析构函数初始化和清理

因为如果采用malloc申请自定义类型,是无法进行初始化的,因为构造函数是在对象定义的时候自动调用

malloc只是申请了空间,无法直接调用构造函数进行初始化,也没办法通过访问成员变量进行初始化(因为一般都是私有的)

③ malloc与new失败时的区别

malloc失败时,会返回NULL

new失败时,会抛异常

测试代码:

//malloc失败
void test3()
{
	char* p1 = (char*)malloc(1024u * 1024u * 1024u * 2);//+u是为了防止整形溢出
	printf("%p\n", p1);//以地址形式打印出p1
}

//输出结果: 00000000
// 即:malloc失败,返回NULL

new失败

char* p2 = new char[1024u * 1024u * 1024u * 2 - 1];//-1是因为规定不能超过0x7fffffff即整形的一半

运行结果:

image-20220911192700761

所以,malloc需要检查返回值看是否malloc失败

new是不需要检查返回值的,失败会抛异常

3. operator new和 operator delete函数(底层)

我们知道,new一个对象其实做了两件事

  1. 申请内存
  2. 调用构造函数

那么new申请内存是调用了谁来申请内存呢?

事实上,new和delete是用户进行动态申请内存和释放操作符,operator new 和 operator delete是系统提供的全局函数

new在底层调用operator new全局函数来申请空间

如图可见:new操作符被解析为指令的时候,主要是 1. call operator new函数 2. call A::A(构造函数)

image-20220911200531507

注意:operator new就是一个函数名,不是运算符重载,只是名字很挫

① operator new和operator delete的底层原理

operator new的源代码

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
    //如果malloc失败,返回NULL 则抛异常
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

可以看到,operator new开空间实际上他也是调用了malloc函数来开空间,只是operator new采用了如果失败就抛出bad_alloc的异常的做法。

这也是为什么要用operator new封装malloc,而不直接采用malloc的原因

总结operator new的作用

  1. 帮助new开空间
  2. 封装malloc,以符合C++new的失败机制(抛异常)

operator delete

/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY
		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY
		return;
}

/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

operator delete是调用free来进行释放资源的

注意:free实际上也是宏定义的,原型是_free_dbg

free是为了方便用户使用

/其他的一些加锁和检查可以忽略/

所以,new/delete对于内置类型其实和malloc/free没有本质区别,因为从底层来看,内置类型不需要调用构造函数和析构函数

总结new的过程:
new Type -> call operator new(malloc失败抛异常) -> call Type构造函数

② 直接使用operator new开辟空间(了解)

operator new这个函数也可以自己单独使用,用法和malloc完全一样;不同的是,operator相比于malloc会抛异常,搭配抛异常的try catch使用

//operator new的使用
	try
	{
		char* ptr = (char*)operator new(1024u * 1024u);//operator new开空间
		printf("%p\n", ptr);//打印出地址
		operator delete(ptr);
	}
	catch (const std::exception& e)
	{
		cout << e.what() << endl;
	}

实际当中,是不需要使用operator new 和 operator delete! 直接使用new和delete即可。

③ operator new和operator delete的重载(了解)

1) 利用重载找出内存泄漏

有时候我们不想用系统提供的operator new和operator delete

那么我们就可以进行重载我们自己的operator new和

operator delete来完成一些特殊的需要

如:我们想要检测哪里存在内存泄漏,这时候就可以自己重载一个operator new,至于operator delete则自己写一个全局的(不是重载)

  1. 我们在自己重载的operator new中打印文件、函数、行号、申请字节数,然后调用全局的 ::operator new

  2. 自己写一个全局的operator delete,这样delete的时候就会先调用自己写的operator delete了

  3. 条件编译,在Debug下,new被宏定义为带着__FILE__,__FUNCTION__,__LINE__三个参数的new,从而实现调用我们自己重载的operator new。C++是允许这么玩的,正常默认只有new的空间大小一个参数传给operator new

注:__FILE__,__FUNCTION____LINE__等是C语言中的宏

分别是当前文件名当前函数名当前行号

// 重载operator new,在申请空间时:打印在哪个文件、哪个函数、第多少行,申请了多少个字节
void* operator new(size_t size, const char* fileName, const char* funcName,
	size_t lineNo)
{
	void* p = ::operator new(size);
	cout <<"new:" << fileName << "-" << funcName << "-" << lineNo << "-" << p << "-"
		<< size << endl;
	return p;
}
//自己实现全局operator delete(使用delete时,就先调用自己写的operator delete)
void operator delete(void* p)
{
	cout << "delete:" << p << endl;
	free(p);
}

// 重载operator delete,这里用不到,只是为了匹配重载的operator new来消除警告
void operator delete(void* p, const char* fileName, const char* funcName,
	size_t lineNo)
{
	cout << "delete:" << fileName << "-" << funcName << "-" << lineNo << "-" << p <<
		endl;
	::operator delete(p);
}

// 使用条件编译和宏对调用进行简化
// 只有在Debug方式下,才调用用户重载的 operator new
// 条件编译需要放在函数重载的下面
//使用 __FILE__这样的宏,作为参数传给自己写的operator new,来实现打印文件名等功能。
#ifdef _DEBUG
#define new new(__FILE__, __FUNCTION__, __LINE__)
#endif
//还需要在main的上面,否则不进行替换程序就走完了

int main()
{
	
	A* p1 = new A;//new的时候 调用重载的operator new
    delete p1;//delete时调用自己写的全局operator delete
    
    A* p2 = new A[4];
    //delete[] p2;
    
    A* p3 = new A;
    delete p3;
    
    A* p4 = new A;
    //delete p4;
    
    A* p5 = new A;
    delete p5;
	return 0;
}
//main函数中 申请5个对象 但是只释放3个  
// 要找出没有释放的两个!

image-20220912093244145

程序运行结果如图

new了5个,但是只delete了3个,并且再通过行号就可以查出具体是哪里没释放而导致的内存泄漏了

2) 重载一个类专属的operator解决频繁申请空间带来的空间碎片问题(内存池)

调用new,编译器会转换为调用 operator new + 构造函数
默认情况下,operator new 使用全局库里面的
但是每个类可以实现自己专属的operator new,new 这个类对象,就会调用这个自己实现的

我们知道,如果一个类 ,比如ListNode类(链表),需要频繁地向内存申请空间
这样就容易造成一些内存碎片问题。这里就可以利用池化技术来减少内存碎片问题,即内存池

内存池的工作原理:

因为malloc是去向堆申请内存,但是要知道操作系统是很忙的,如果频繁的申请就会经常打断操作系统的资源的分配

而内存池就相当于中间的一个角色。内存池先申请一部分内存,当你想要开辟空间首先到内存池中开辟,当内存池中的

内存用完,才会再去向堆上申请。这样就减少了请求操作系统的次数,提高效率。

用个比喻的话就是,堆是你妈妈的钱包,内存池是你自己的钱包。你的定期生活费就是内存池提前申请的内存

我们可以在类中重载一个operator new和operator delete函数。(注意不是函数重载,也不是运算符重载,只是命名空间即域不同)在我们重载的函数中使用内存池(可以自己写,也可以调用STL或者Boost库或第三方库中的内存池)

当new一个对象和delete一个对象的时候,机制决定了会先去类中找有没有类专属的operator new 和 operator delete (局部就近原则)

找到了就直接使用,找不到才回去全局找。并且因为是类专属的,其他的类的new/delete并不受影响

所以会直接调用我们在类中重载的operator new和operator delete函数,也就是先去我们定制的内存池中申请内存而不会直接向堆上申请内存

这里演示STL中的内存池allocator(空间配置器)

allocate:申请空间

deallocate:释放空间

//重载类专属operator new
struct ListNode
{
	int _val;
	ListNode* _next;
	//内存池:给所有ListNode用
	static allocator<ListNode> _alloc;//把内存池设置为静态成员变量(声明)
	//每个ListNode对象都可以访问到,属于整个类
	void* operator new(size_t n)
	{
		cout << "void* operator new(size_t n) -> STL内存池allocator申请" << endl;
		//allocator类
		void* obj = _alloc.allocate(1);
		return obj;
	}
	void operator delete(void* ptr)
	{
		cout << "void* operator delete(size_t n) -> STL内存池allocator释放" << endl;
		_alloc.deallocate((ListNode*)ptr,1);
	}
	ListNode(int val)
		:_val(val)
		, _next(nullptr)
	{

	}

};
//类外面定义内存池对象
allocator<ListNode> ListNode::_alloc;//默认构造
int main()
{
    //频繁申请ListNode 想提高效率  申请ListNode时,不去malloc而是走自己定制的内存池
	ListNode* node1 = new ListNode(1);
	ListNode* node2 = new ListNode(1);
	ListNode* node3 = new ListNode(1);
	delete node1;
	//delete node2;
	//这里还可以作内存泄漏的检测 申请和释放的个数不符合就是内存泄漏
	delete node3;
	return 0;
}

image-20220911222935774

很容易看出存在一个内存泄漏

ps:(其实更官方一些的检查内存泄漏的并不是这样一个个数,而是用一个数据结构,在operator new的时候把地址存进来

operator delete的时候把它删除,最后进行查找,剩下的就是没被释放的)

4. new/delete的实现原理

① 内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:

new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申

请空间失败时会抛异常,malloc会返回NULL

② 自定义类型
  • new的原理
    1. 调用operator new函数申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造
  • delete的原理
    1. 在空间上执行析构函数,完成对象中资源的清理工作
    2. 调用operator delete函数释放对象的空间
  • new T[N]的原理
    1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
      象空间的申请
    2. 在申请的空间上执行N次构造函数
  • delete[]的原理
    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
    2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释
      放空间

5. 定位new表达式(Replace new)(了解)

我们知道,构造函数的调用时机常见的就是:

  1. 直接创建对象的时候,自动调用构造函数初始化
  2. new对象的时候,自动调用构造函数

但是上面两种方式都是在开空间创建对象的时候调用构造,我们是不可以显示调用构造函数的(可以显示调用析构函数)

并且也不可以访问成员变量直接初始化

那么如何对已经分配好空间的对象调用构造函数来初始化呢? 这就是定位new的作用

**定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象 **

但是析构的话需要自己显示调用

使用格式:

new (place_address) type或者new (place_address) type(initializer-list)
**place_address必须是一个指针,initializer-list是类型的初始化列表 **

// 对malloc或者operator new的空间 用定位new调用构造函数初始化
int main()
{
	//p2现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没
	//	有执行
	A* p2 = (A*)malloc(sizeof(A));
	if (p2 == NULL)
	{
		perror("malloc fail");
	}
	//定位new 初始化已经开辟好的空间
	//new(p2)A; //不给参数
	new(p2)A(10);//给参数

	//free p2前需要手动析构,释放资源
	p2->~A();
    free(p2);//free p2
	return 0;
}

使用场景

相比new来说,new即开了空间还自动初始化,为啥还要有定位new呢?

这里就还是因为存在 内存池 的应用场景

如果我们去内存池申请空间,那么内存池只是开空间,并不会调用构造函数初始化

所以我们要对开好的空间进行初始化就必须使用定位new

扩展阅读:
C++重载 operator new 和 operator delete 实现一个简单内存泄漏跟踪器

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

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

相关文章

小面馆叫号取餐流程 佳易王面馆米线店点餐叫号管理系统操作教程

一、概述 【软件资源文件下载在文章最后】 小面馆叫号取餐流程 佳易王面馆米线店点餐叫号管理系统操作教程 点餐软件以其实用的功能和简便的操作&#xff0c;为小型餐饮店提供了高效的点餐管理解决方案&#xff0c;提高了工作效率和服务质量 ‌点餐管理‌&#xff1a;支持电…

单体架构 IM 系统之 Server 节点状态化分析

基于 http 短轮询模式的单体架构的 IM 系统见下图&#xff0c;即客户端通过 http 周期性地轮询访问 server 实现消息的即时通讯&#xff0c;也就是我们前面提到的 “信箱模型”。“信箱模型” 虽然实现非常容易&#xff0c;但是消息的实时性不高。 我们在上一篇文章&#xff08…

大语言模型理论基础

文章目录 前言大语言模型必需知识概述大语言模型目标模型上下文神经网络的神经元常见激活函数SigmoidTanhRelusoftmax 通用近似定理多层感知机&#xff08;MLP&#xff09;拟合最后 前言 你好&#xff0c;我是醉墨居士&#xff0c;我们接下来对大语言模型一探究竟&#xff0c;…

37.安卓逆向-壳-smali语法1

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信。第一…

Arduino IDE Windows 系统 离线安装 esp32 开发板 亲测好用。

1、前提条件需要具备特殊网络。 2、官方文档地址&#xff1a;Installing - - — Arduino ESP32 latest documentation 3、系统&#xff1a;Windows10 Arduino IDE 版本2.3.3 之前安装的esp32开发板的版本是2.0.13&#xff0c;由于之前没有接触过esp32开发&#xff0c;也没…

使用HTML、CSS和JavaScript创建动态圣诞树

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

OceanStor Pacific系列 8.1.0 功能架构

功能架构 华为OceanStor Pacific系列提供基于三层的分布式存储架构&#xff0c;融合分布式文件、对象、大数据和块多个服务形态&#xff0c;支持文件、对象、大数据服务部署在一个集群&#xff0c;并统一管理。 华为OceanStor Pacific系列整体功能架构由存储接口层、存储服务…

图像处理实验二(Image Understanding and Basic Processing)

图像理解&#xff08;Image Understanding&#xff09;和基本图像处理&#xff08;Basic Image Processing&#xff09;是计算机视觉领域的重要组成部分。它们涉及从图像中提取有用信息、分析图像内容、并对其进行处理以达到特定目的。图像理解通常包括识别、分类和解释图像中的…

uniapp 实现tabbar分类导航及滚动联动效果

思路&#xff1a;使用两个scroll-view&#xff0c;tabbar分类导航使用scrollleft移动&#xff0c;内容联动使用页面滚动onPageScroll监听滚动高度 效果图 <template><view class"content" ><view :class"[isSticky ? tab-sticky: ]">…

aws xray通过设置采样规则对请求进行过滤

参考资料 https://github.com/aws/aws-xray-sdk-pythonpython api reference&#xff0c;https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/node api reference&#xff0c;https://docs.aws.amazon.com/xray-sdk-for-nodejs/latest/reference/ 初始化环境…

【征稿倒计时!华南理工大学主办 | IEEE出版 | EI检索稳定】2024智能机器人与自动控制国际学术会议 (IRAC 2024)

#华南理工大学主办&#xff01;#IEEE出版&#xff01;EI稳定检索&#xff01;#组委阵容强大&#xff01;IEEE Fellow、国家杰青等学术大咖领衔出席&#xff01;#会议设置“优秀论文”“优秀青年学者报告”“优秀海报”等评优奖项 2024智能机器人与自动控制国际学术会议 &#…

Unity3D学习FPS游戏(12)敌人检测和攻击玩家

前言&#xff1a;上一篇实现了敌人能动&#xff0c;有了点乐趣&#xff0c;但是敌人和玩家没什么对抗性。本篇将实现敌人追击玩家&#xff0c;并攻击玩家。 敌人攻击玩家 敌人检测玩家目标思路-碰撞检测的Trigger触发实现 敌人攻击目标思路-模仿玩家发射子弹的思路实现 效果 敌…

nginx代理后jsp如何获取http协议

1. nginx配置增加返回协议类型&#xff08;http或https&#xff09; location / {proxy_set_header X-Forwarded-Proto $scheme; } 2. 修改jsp配置 原jsp配置&#xff1a; <%String basePath request.getScheme()"://"request.getServerName()":"r…

#渗透测试#SRC漏洞挖掘#蓝队基础之网络七层杀伤链01

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

uni-app移动端与PC端兼容预览PDF文件

过程遇到的问题 1、如果用的是最新的版本的pdfjs的话&#xff0c;就会报Promise.withResolvers 不是一个方法的错误&#xff0c;原因是Promise.withResolvers是ES15新特性&#xff0c;想了解可参考链接&#xff0c;这里的解决方案是将插件里的涉及到Promise.withResolvers的地…

HBase使用create创建表时报错ERROR: KeeperErrorCode = NoNode for /hbase/master

场景模拟 1. 正常情况 模拟ERROR: KeeperErrorCode NoNode for /hbase/master错误场景。 正常情况下创建hbase表如下图所示。 2. 删除hbase集群的zk节点 进入zookeeper客户端。 zkCli.sh删除hbase的zk节点。 deleteall /hbase退出zookeeper客户端。 quit3. 重启hbase集…

前端web

题目&#xff1a;制作带有下拉悬停菜单的导航栏 效果图 一、先制作标签 <body> <div id"menu"> <div id"container"> <div class"item">游戏1 <div class…

实测运行容器化Tomcat服务器

文章目录 前言一、拉取Tomcat 9.0镜像二、运行容器化Tomcat服务器三、访问Tomcat官网首页测试 总结 前言 运行容器化Tomcat服务器&#xff0c;首先确保正确安装docker&#xff0c;并且已启动运行&#xff0c;具体安装docker方法见笔者前面的博文《OpenEuler 下 Docker 安装、配…

PHP图书绘本借阅管理系统小程序源码

&#x1f4da; 图书绘本借阅管理系统&#xff1a;打造孩子的阅读乐园 &#x1f4da; &#x1f3f7;️ 引言&#xff1a;为什么我们需要图书绘本借阅管理系统&#xff1f; 在孩子的成长旅程中&#xff0c;阅读是不可或缺的一部分。然而&#xff0c;面对琳琅满目的图书和绘本&a…

炼码LintCode--数据库--基础语法--刷题笔记_01

目录 炼码LintCode数据库入门级别的笔记未完待续~~~ 炼码LintCode 数据库 入门级别的笔记 笔记如下&#xff0c;把所有涉及到的入门级别的知识点简单总结了一下。 以及一点点举一反三的写法。 增 INSERT INTO 表名 (列1, 列2, ...) VALUES (值1, 值2, ...);批量增 INSERT INT…