C/C++ 内存管理 new delete operator new与operator delete函数 内存泄漏

news2024/11/27 10:33:25

C/C++ 内存分布

 在C/C++ 当中有 :

  • 局部数据
  • 静态数据和全局数据
  • 常量数据
  • 动态申请数据

 上述不同的数据存储的位置也不同,:

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

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)吗?
	free(p3);
}

 上述的 p2 不一定需要 free,解释在下。此时只需要做的是,把 p2 这个指针给置空。

问:

malloc , realloc , calloc  的区别:

  • malloc 的空间不会初始化,而 calloc 的空间会初始化。
  • realloc 是对已有的空间进行阔扩容,而且realloc 函数传入的需要扩容的空间必须的是 malloc, calloc,或者是realloc的空间,假设如果是栈上的空间是,那么传入这个空间是不能使用 realloc函数进行扩容的。
  • 而且 realloc 函数扩容分为两种, 一种是原地扩容,如果在原空间的后面,有足够的的空间进行扩容,那么就直接在后面进行扩容,此时返回的指针就是原空间的首地址;另一种是 异地扩容,如果原空间后面 没有足够的空间,那么realloc就会重新开辟空间,然后把 原空间当中的 数据拷贝过来,然后再把袁孔建free掉,返回的指针是新开辟空间的 首地址。

malloc 的实现原理:【CTF】GLibc堆利用入门-机制介绍_哔哩哔哩_bilibili

 C++内存管理方式

 C++ 当中可以使用在 C 当中使用的 内存管理的方式,但是C++当中也有自己的实现方式。

使用 new / delete 来创建和销毁空间

int main()
{
	// C 当中的玩法
	int* p1 = (int*)malloc(sizeof(int));

	// C++ 当中的玩法
	// 创建一个 int 大小的 空间
	int* p2 = new int;

	// 创建三个 int  大小的 空间
	int* p3 = new int[3];

	// 创建 一个 int 大小的 空间,并且把这个空间当中的值初始化为 10
	int* p4 = new int(10);

    delete p1;
	delete p2;
	delete[] p3;
	delete p4;

	return 0;
}

需要注意的是 如果是多个 某类型大小的,那么在使用 delete的时候需要在后面加一个 " [] "。

  •  在new当中在类型的后面使用 " () "  来给这个空间当中给初始值。
  •   在new当中在类型的后面使用 " []  "  来决定创建的空间有几个类型大小。
  • 在new当中初始化一个数组,不能用 " () "  而是直接使用 " { } " 来一个元素,按照下标的顺序来初始化

 如果我们初始化没有初始化完,剩余没有初始化的会初始化为 0 :

 这里的new 对于内置类型,效果都差不多,C++当中的 new 主要针对的是 自定义类型,比如我们创建一个  单链表的结点,在C当中是这样的:

typedef struct ListNode
{
	int _val;
	struct ListNode* next;
}ListNode;

ListNode* BuynewNode(int x)
{
	ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	tmp->_val = x;
	tmp->next = NULL;

	return tmp;
}

int main()
{
	// C 实现
	ListNode* p1 = BuynewNode(1);
	ListNode* p1 = BuynewNode(2);
	ListNode* p1 = BuynewNode(3);

	return 0;
}

因为malloc 是单纯的开辟空间,不能进行初始化,所以我们要自己进行初始化,而且malloc 有可能会开辟失败,我们还要判断一下。

就算使用 calloc 也不好,也 calloc 只能全部初始化为  0 ,而有的时候我们不想要都赋值为0,所以这就是 在C++当中要 新增 new的原因,当我们 new 一个自定义的类型的对象的时候,就可以 同时调用这个对象的构造函数,其中就可以进行初始化:

struct ListNode
{
	int _val;
	struct ListNode* _next;

	ListNode(int x)
		:_val(x)
		,_next(NULL)
	{}
};

int main()
{
	ListNode* nn1 = new ListNode(1);
	ListNode* nn1 = new ListNode(2);
	ListNode* nn1 = new ListNode(3);

	return 0;
}

向上述就是 使用new  (开空间 +  调用构造函数初始化),不用再像 C 当中使用 malloc 函数,需要写个函数来 初始化,而且 还需要判断 malloc 是否成功,调用 malloc 需要计算 类型的大小,还需要把malloc函数的返回值 强转 为 我们对应类型的指针,等等的这些复杂操作。

malloc 和 free 就只是开辟空间和 释放空间,不会进行初始化,而且我们在使用 new 和 delete 的时候就会调用这个自定义类型的构造函数和 析构函数:

 我们把调用析构函数和 构造函数时,打印一下这个函数名称,如上述所示,我调试发现, new 和 delete 都会调用 对应的 构造函数和 析构函数。

如果没有默认的构造函数,我们还可以使用 {} 来传参:

class A
{
public:
	A(int a)
	{
		_a = a;
		cout << "A(int a = 10)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

protected:
	int _a = 0;
};

int main()
{
    A* a1 = new A[4]{ 1,2,3,4 };

	return 0;
}

如上述类 A 是没有默认构造函数的,如果这个类当中构造函数只有一个参数,那么我们就可以直接传入 对应类型的 数据,这里就会发生隐式类型转换。

而如果是多个参数的,如下所示:

	A(int a, int b)
	{
		_a = a;
		_b = b;
		cout << "A(int a = 10)" << endl;
	}

那么我们可以使用 匿名对象的方式传参,进行初始化:

	A* a1 = new A[4]{ A(1,1),A(1,1), A(1,1), A(1,1) };

而且此时我们不能使用 之前一个参数的时候传参的方式了:

 而且像上述的 使用匿名对象来赋值的 编译器会进行优化,不会调用拷贝构造函数。

如果,没有默认构造函数,那么这个 数组中有多少个 对象,那么就必须对这个数组当中的每一个对象都要进行初始化;如果有默认构造函数,那么我们就可以选择性的选择几个对象来进行初始化。

 

 还需要注意是的,如果使用 new 来开辟空间,就必须使用 delete 来释放空间;如果使用 malloc 等等函数来开辟空间,就是用 free 来释放空间;如果反过来用的话,可能会出现问题。为了避免出现问题,那么还是配套使用,不要随便使用。

 operator new与operator delete函数

 我们上述使用 new 和 delete 都是操作符,而在系统当中其实还实现了 operator new与operator delete这两个全局函数。其实我们在使用  new 和 delete 两个操作符的时候,其底层也是调用operator new与operator delete这两个全局函数。

而new 和 malloc 的最大区别就是 new 在自定义的时候会调用构造函数。但是new 也是需要开空间的,他只不过是 在开空间的基础之上,针对 自定义类型 ,它多调用了一次构造函数其实new 开空间就是调用 C 当中的malloc 函数来在堆当中开空间的。

 那么既然是使用 malloc 函数来在堆当中开辟空间,但是我们知道,如果malloc 函数开辟空间失败之后,就会返回一个空指针,C++是面对对象的语言,面对对象的语言在出错的时候不喜欢像C 一样返回一个 -1 ,它喜欢抛出异常,那抛出异常就需要捕获异常。

例如,我们写一个循环,一直 malloc 空间,直到 malloc 放回空指针为止:

 我们发现这个程序就是正常停止的,而如果我们使用new来开辟空间,同样使用这个类型的代码实现:
 

 此时发现,它报了异常:

 那么捕获异常,大多数语言都差不是,像java 当中就是 使用 try {} catch{} 来捕获异常,C++也是使用这个来捕获异常:

 发生异常我们就 可以直接 catch 当中的处理,上述的打印值:

 那么其实 new 操作符 调用的 operator new 函数也是实现了类型上述的功能,这个operator new函数其实就是对 C 当中的malloc函数进行的封装处理,一下是他的源代码:

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	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 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 在 C当中实现:

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

而 operator delete函数 也是一样使用的是 free()来释放空间,也是对这个free()函数就行了封装。

 至此,我们就了解了 operator new与operator delete 这两个函数功能,我们也可以使用这两个函数,但是其实这两个函数不是给我们使用的,而是给 new 和 delete 来使用的,他们之间的关系如下图所示:

 而 delete 也是差不多的:

 我们查看 new 的反汇编也可以看出:

 new和delete的实现原理

 内置类型

 new和delete 在内置类型方面其实和 malloc 与 free 没有多大区别,只是 new 在开辟空间失败之后是抛出异常;而malloc 是返回一个 NULL 指针。而且 new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间。

 自定义类型

  对于new 和 delete 我们上述也说过了,new 是先调用 operator new 函数,来开辟空间,然后调用这里类型的构造函数;而 delete 是先调用西沟函数,然后再 调用  operator delete  释放空间。

这里主要是提, new[]  和 delete[] 这两个,顺序和上述是一样的,只是调用N次而已:

  • new[]  是调用operator new函数完成N个对象空间的申请,然后在申请的空间上执行N次构造函数
  • delete[]  是在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理,再调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
typedef int STDataType;
class Stack
{
public:
	Stack(size_t a = 3)
	{
		cout << "Stack(size_t a = 1)" << endl;

		_array = (STDataType*)malloc(sizeof(STDataType) * _capacity);
		if (_array == NULL)
		{
			perror("malloc fail");
			return;
		}

		_capacity = a;
		_size = 0;

	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

protected:
	STDataType* _array;
	int _size;
	int _capacity;
};

int main()
{
	Stack* ST = new Stack(3);

	return 0;
}

请问上述中的     Stack* ST = new Stack(3);  在内存当中是如何存储的。

ST是这个类型的指针,那么他是存储在栈上的;我们 new 出来的空间(调用operator new函数)当中存储的就是 Stack 类创建的对象当中存储的 成员:

 然后,再去调用构造函数,在堆上开辟空间,开辟一段数组空间,然后让 对象当中的 _array 指针指向这块空间的首地址。

 这里就是我们使用 new 创建空间的 过程,和各个变量在内存当中的分布。

我们使用delete 也是一样的,只不过他是先 调用析构函数, 再去 调用 operator delete[] 释放空间。像上述例子,就是先调用 析构函数当中的 free 掉_arrray 指针指向的 malloc 开辟的空间,然后再调用 operator delete[] 函数来释放掉 堆当中开辟的 对象的空间。

 像上述的释放空间的过程是不能 反过来的,如果先free 掉 对象空间就找不到 其中 开辟的 数组空间了。

 定位new表达式

 new 除了可以开空间,如果是自定义类型会掉这个类型的构造函数对这个自定义类型进行初始化。

new 还可以 对已有的一块空间 ,显示调用构造函数,来对这个空间当中的成员进行初始化:
比如像上述的Stack 这个类:

	Stack* ST = (Stack*)malloc(sizeof(Stack));
	new(ST)Stack(3);

如果在内存当中已经有这个类开辟的一个空间,那么我们就可以显示的调用构造函数,读这个空间当中的成员进行初始化。

使用语法:

new (place_address) type   或者   new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

像上述的情况我们还可以显示的调用 析构函数来对这个对象的当中的成员空间进行 释放:

	ST->~Stack();
	free(ST);

像上述的过程我们其实就是在模拟 new 和 detele  的过程。

问:向上述的情况,如果我们不显示调用 ST 的析构函数,它会自动调用 析构函数吗?

 答:不会。因为只有自定义类型才会自动调用 构造函数和 析构函数。像上述的 ST 是 Stack* 这个类型的,是一个指针,是内置类型,所以不会自动调用。

 使用场景

 如果我们想要实现高效率的代码,那么有一种方法叫做 池化技术,他就是创建出很多很多的池来进行复用,从而来提升性能。如:对象池通过复用对象来减少创建对象、垃圾回收的开销。还有 连接池,内存池,线程池·······

这就好比说是,一个人住在山顶上,但是水源在山脚,那么他每一次需要水的时候,都要去山脚去取水,那么这个人做的其他的事情就会被 耽搁。那么我们就像,在山腰处 做一个蓄水池,那么这个人需要水的时候,就只需要到山脚处取水就行了,他就能更好的完成它的本职工作,从而提升工作效率。

那么,当我们从某一个池子当中,拿出一个 一块开辟好的 某一个自定义类型的空间,那么这时候我们想要去 对这个空间进行初始化,那么就要使用之前说过的 new 用法来实现了。如下例子:

Node* n1 = pool.alloc.(sizeof(Node));
new(n1)Node(2);

小结

  •  new 和 malloc 的区别 ,  free 和 detele 的区别。
  •  malloc 和 free 是函数,而 new 和 detele 是操作符
  • malloc 不能对开辟的空间进行初始化,而new可以初始化。
  • malloc 在开辟空间的时候需要手动计算需要开辟空间的大小,而new 就值需要在后面跟上数据类型即可,如果需要开辟多个这个类型,那么只需要在之后 使用 []  来指定个数即可。
  • malloc 函数的返回值是 void*  ,如果我们想要接收这个返回值,那么我们需要把这个指针 强转为我们需要的 指针类型。而new不需要,因为new后面跟的就是空间的类型。
  • malloc 开辟空间失败之后,返回的是 NULL 指针;而 new 开辟空间失败 是抛出异常。
  • 对于自定义类型  malloc  和  free 在 开辟 和 释放空间的时候,就只是开辟和释放空间;而 new 和 detele 是除了开辟和释放之外,分别会 调用 自定义类型的 构造函数 和 析构函数,以此来初始化对象和 销毁空间中的 资源。

 内存泄漏

 指的是,如果我们在写程序可能会疏忽或者是错误,导致程序未能释放程序已经不在使用的内存的情况。内存泄漏指的不是内存消失了,而是由于设计的失误,失去了对这块内存的 控制,因而造成了浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。

int main()
{
	int* ptr1  = (int*)malloc(sizeof(int));
	int* ptr2 = new int;

	func();// 由于 func 函数未定义,导致抛出异常,程序异常结束,ptr2 没有来得及 释放

	delete ptr2;

	return 0;
}

 如上诉 的ptr1 和 ptr2 都每一释放空间,都属于内存泄漏。

而我们在写这些小程序的时候,多次出错,我们使用的代码环境都没有奔溃,不仅仅是我们写的程序体量太小的原因,很大原因是,我们的操作系统不会容忍一个进程 出现内存泄漏,当一个进程结束之后,他会自定把使用的内存都 回归使用权限。

内存泄漏影响的主要是 ,长期运行的程序,如果程序长期运行,那么就会有很大危害。如游戏服务,打车服务····等等。其实内存泄漏多的还好,因为可能很快就这个程序就会挂掉,那么我们检查日志相对容易发现;如果每天只是泄漏一点,比如 10MB ,那么这个程序可能在很久之后 才会奔溃,那么检查起来没有那么容易。

内存泄漏的 分类

 在C/C++ 的程序当中一般都一下两种 类型泄漏:

  • 堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

  • 系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
 

 内存泄漏的检测

 在vs下,可以使用 windows  操作系统提供  的函数 _CrtDumpMemoryLeaks() 函数进行简单的检测。但是这个函数只能报出有多少的字节被泄漏了,没有具体信息。

在linux下内存泄漏检测:Linux下几款C++程序中的内存泄露检查工具_testsuite kmalloc_CHENG Jian的博客-CSDN博客

 在windows下使用第三方工具:

VS编程内存泄漏:VLD(Visual LeakDetector)内存泄露库_波波在学习的博客-CSDN博客

 其他工具:
内存泄露检测工具比较 - 默默淡然 - 博客园 (cnblogs.com)

 

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

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

相关文章

Java运算符详解

目录 &#x1f511;前言 &#x1f3bd;算式运算符 ⚙赋值运算符 &#x1f34a;关系运算符 &#x1f37a;逻辑运算符 &#x1f352;位运算符 &#x1f339;移位运算符 &#x1f343;条件运算符 &#x1f349;运算符优先级 &#x1f511;前言 任何一个程序都离不开计算问题&…

IOC/DI配置管理第三方bean(Druid/C3P0)

文章目录 1 案例:数据源对象管理1.1 环境准备1.2 思路分析1.3 实现Druid管理步骤1:导入druid的依赖步骤2:配置第三方bean步骤3:从IOC容器中获取对应的bean对象步骤4:运行程序 1.4 实现C3P0管理步骤1:导入C3P0的依赖步骤2:配置第三方bean步骤3:运行程序 2 加载properties文件2.1…

详解MySQL的并发控制

目录 1.概述 2.事务 2.1.什么是事务 2.2.事务的隔离级别 2.2.1.三种数据一致性问题 2.2.2.四种隔离级别 2.3.如何设置隔离级别 3.锁 3.1.锁与事务的关系 3.2.分类 3.3.表锁 3.3.1.概述 3.3.2.读锁 3.3.3.写锁 3.3.4.保护机制 3.4.行锁 3.4.1.概述 3.4.2.什么…

Redis Java API操作

1、普通maven工程方式 Redis不仅可以通过命令行进行操作&#xff0c;也可以通过JavaAPI操作&#xff0c;通过使用Java API来对Redis数据库中的各种数据类型操作 导入POM依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http…

【Linux Network】传输层协议——UDP

目录 传输层 端口号 端口号范围划分 认识知名端口号(Well-Know Port Number) netstat pidof UDP协议 UDP协议端格式 UDP的特点 面向数据报 UDP的缓冲区 UDP使用注意事项 基于UDP的应用层协议 UDP详解&#x1f337; 传输层 在TCP/IP协议中可以把网络简单的划分为四个部分&#…

大数据如何助力营销(3)产品定位

在市场竞争日益激烈的环境下&#xff0c;产品定位是企业成功的关键因素之一。产品定位是指根据目标市场和目标消费者的需求、偏好和期望&#xff0c;确定产品的特性、功能、形象和价值&#xff0c;并与竞争对手的产品进行差异化的过程。产品定位不仅影响产品的设计、开发、生产…

全面讲解涂鸦PaaS2.0开发平台!物联网干货预警

之前我们有介绍过涂鸦 IoT PaaS&#xff08;点击查看往期介绍&#xff09;&#xff0c;面向开发生态&#xff0c;它集成了云开发、App 开发、硬件开发三大核心支撑能力&#xff0c;能够全方位助力开发者打造极具竞争力的个性化 IoT 解决方案&#xff0c;极大地降低 IoT 开发门槛…

Appium APP自动化环境搭建

1.下载安装 F:\android-sdk-windows F:\Appium-windows-1.21.0 F:\nodejs 2.创建一个bat文件&#xff0c;命名为appium.bat&#xff0c;并在其中写入如下内容&#xff1a; node Appium安装目录\resources\app\node_modules\appium\build\lib\main.js %* 注意&#xff1a;请…

从0到1开始,一步步搭建Web自动化测试框架

测试框架的设计有两种思路&#xff0c;一种是自底向上&#xff0c;从脚本逐步演变完善成框架&#xff0c;这种适合新手了解框架的演变过程。另一种则是自顶向下&#xff0c;直接设计框架结构和选取各种问题的解决方案&#xff0c;这种适合有较多框架事件经验的人。本章和下一张…

【zabbix】批量监控端口,自动发现规则

快速搞定端口批量监控 一、脚本及配置 1、&#xff08;文件名&#xff1a;check_port.py&#xff09;&#xff08;python2.7版本&#xff09; 存在路径&#xff1a;/etc/zabbix/zabbix_agentd.d/check_port.py 这个脚本有一部分内容是我从百度上找的&#xff0c;有一部分自己…

【C++】红黑树的实现

文章目录 &#x1f4d5; 概念特性 &#x1f4d5; 红黑树具体实现节点定义结构框架插入 &#x1f4d5; 概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个…

Python基础(四)

目录 一、程序的组织结构 1、前言 二、顺序结构 1、介绍 三、对象的布尔值 1、介绍 2、规定 四、分支结构 1、单分支if结构 1、语法语义 2、语法结构 3、案例 2、双分支if...else结构 1、语法语义 2、语法结构 3、案例 3、多分支if...elif...else结构 1、语…

Java语言---栈与队列

目录 一.栈 1.1栈的概念 1.2.栈的实现 1.2.1数组实现 栈 栈的创建 栈的基本方法实现 1.2.2链表实现 栈 栈的创建 栈的基本方法实现 二.队列 2.1队列的概念 2.2队列的实现 2.3代码实现 2.3.1队列代码的构建 2.3.2 队列 基础方法实现 总结 &#x1f63d;个人主页…

深入理解2D卷积和3D卷积

文章目录 卷积核的维度2D卷积单通道多通道代码example2d卷积操作后变化 3D卷积单通道多通道代码 在项目中用到了conv3但是对其背后的原理还有一些模糊的地方&#xff0c;conv2d与多通道的conv2d的区别在哪里&#xff1f;conv3d的思想理论是什么&#xff1f;对此进行探究和记录……

「AI之劫」:当机器超越人类底线,正在侵犯我们的创造力和道德

随着AI技术的不断发展&#xff0c;AI生成内容&#xff08;AIGC&#xff09;已经成为数字娱乐、商业营销和学术研究等领域的热门话题。随着人工智能技术的不断发展越来越多的领域开始应用AI技术&#xff0c;其中之一就是内容生成领域。 AIGC全称为AI-Generated Content, 指基于生…

2023年5月广州/深圳制造业产品经理很适合考的证书-NPDP

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

【利用AI让知识体系化】入门Egg框架(含实战)

思维导图 文章目录 思维导图第一章&#xff1a;概述1.1 Egg.js 简介1.2 Egg.js 的架构和优势1.3 Egg.js 的基本组件和插件 第二章&#xff1a;环境搭建2.1 Node.js 环境安装和配置2.2 Egg.js 应用创建和项目结构介绍2.3 Egg.js 应用部署和启动 第三章&#xff1a;基本开发3.1 路…

经纬恒润新产品系列 | 这款AR-HUD将颠覆你的认知

随着科技的发展与突破&#xff0c;智能化产品在汽车领域扮演了越来越重要的角色。本文即将介绍**经纬恒润新产品——AR-HUD&#xff08;增强现实抬头显示系统&#xff09;&#xff0c;**它可以将科幻电影中的驾驶场景变为现实——将信息投影在挡风玻璃上&#xff0c;基于此功能…

开发环境搭建和创建STM32工程

目录 一、开发环境搭建 1. STM32CubeMX 2.Keil安装 二、创建STM32工程 一、开发环境搭建 1. STM32CubeMX ST公司出品 工具链接 https://www.st.com/zh/development-tools/stm32cubemx.html STM32CubeMX是一种图形工具&#xff0c;通过分步过程可以非常轻松地配置STM32微控制器和…

coolshell 镜像备份站点

缅怀技术大佬做的一个镜像站点 - RIP 消息刚开始是在推特传开&#xff0c;后面得到了家人同事的证实。噩耗&#xff01; worldpeople2019 太意外了&#xff01;中年程序员&#xff0c;感觉年龄跟我差不多&#xff0c;怎么就这么突然去世了&#xff1f;&#xff01;诸位码农朋友…