【C++破局】C++内存管理之new与deleted剖析

作者主页

📚lovewold少个r博客主页

   ⚠️本文重点c++内存管理部分知识点梳理

👉【C-C++入门系列专栏】博客文章专栏传送门

😄每日一言:花有重开日,人无再少年!

目录

C/C++的内存分配机制

内存分区

1. 内核空间(Kernel Space):

2. 栈空间(Stack):

3. 内存映射段(Memory Mapping Segment):

4. 堆(Heap):

5. 数据段(Data Segment):

6. 代码段(Code Segment):

C与C++的动态内存管理方法

malloc,calloc,realloc的内存开辟函数

内存泄露  

C++的内存管理方式

new/delete操作内置类型

 new的基本用法

delete的基本用法

malloc/free和new/delete的区别

operator new与operator delete函数

重载operator new和operator delete(了解)

new和delete的底层原理

new的底层原理:

delete的底层原理:

定位new表达式

总结


前言

        在c语言的学习过程中,我们学习了c语言的动态管理内容。本章的内容主要是从C/C++的内存分配区域入手,深入浅出的讲解C++的内存分配机制和new与delete。

C/C++的内存分配机制

内存分区

1. 内核空间(Kernel Space)

   位置: 在内存的顶部,通常是最高的地址空间。
   用途:内核空间用于存放操作系统的内核代码和数据结构。这部分内存通常是受保护的,只有内核态的代码可以访问。

2. 栈空间(Stack)

   栈是向下增长的:对于栈来说,栈内存的地址是从高地址向低地址增长的。新的栈帧会被放置在当前栈顶的上方。栈帧的释放会导致栈顶向低地址移动

    位置:通常位于用户空间和内核空间之间。
    用途:栈用于存储函数的局部变量、函数参数、返回地址和函数调用的上下文信息。栈是一个后进先出(LIFO)的数据结构。
    自动分配和释放:栈内存是由系统自动分配和释放的,函数调用时分配,函数返回时释放。

3. 内存映射段(Memory Mapping Segment)

    位置:通常位于用户空间和内核空间之间。
    用途:内存映射段包括可执行文件的映射、共享库的映射以及动态链接器的映射。这些映射将磁盘上的二进制文件映射到内存中,以便执行。

4. 堆(Heap)

   堆是向上增长:堆的内存地址是由低地址向高地址增长的,新分配的内存位于已经分配的内存的上方。

   位置:通常位于内核空间和栈空间之间。
   用途:堆用于动态分配内存,程序员可以通过函数如 malloc、calloc、realloc 来在堆上分配内存。堆上的内存需要手动释放,否则可能导致内存泄漏。
   动态大小:堆的大小不是固定的,可以根据需要动态分配和释放。

5. 数据段(Data Segment)

   位置:通常位于内核空间和栈空间之间。
   用途:数据段包括已初始化的全局变量和静态变量。这些变量在程序启动时就被初始化,并在整个程序的执行周期内保持不变。

6. 代码段(Code Segment)

   位置:通常位于内核空间和栈空间之间。
   用途:代码段包含程序的机器代码,即可执行指令。这部分内存是只读的,用于存储程序的执行代码。

只读数据段中,包含了两种常量:

1. 字符串常量:
   例如,C/C++中的字符串字面值(如"Hello, World!")。
   这些字符串常量存储在只读数据段中,因为它们在程序执行期间是不可修改的。
2. 其他常量:
   例如,C/C++中的全局常量(使用 const 关键字声明的常量)。
   这些常量也通常存储在只读数据段中。


C与C++的动态内存管理方法

malloc,calloc,realloc的内存开辟函数

malloc, calloc, 和 realloc 是在C语言中用于分配内存的三个常见函数。

malloc(Memory Allocation):

示例:

void Test()
{int* p1 = (int*)malloc(sizeof(int));if (p1 == NULL){perror("malloc");exit(EXIT_FAILURE);}free(p1);
}

  • 原型:void* malloc(size_t size);

  • 功能:用于动态分配指定大小的内存块。

  • 参数:size 表示要分配的字节数。

  • 返回值:如果分配成功,返回指向分配内存的指针;如果失败,返回 NULL

calloc(Contiguous Allocation):

示例:

#include<stdlib.h>
void Test()
{int* p1 = (int*)calloc(4,sizeof(int));if (p1 == NULL){perror("calloc");exit(EXIT_FAILURE);}free(p1);
}
  • 原型:void *calloc(size_t num_elements, size_t element_size);

  • 功能:用于动态分配指定数量和大小的内存块,并将每个字节初始化为零。

  • 参数:num_elements表示要分配的元素数量,element_size表示每个元素的大小(字节数)。

  • 返回值:如果分配成功,返回指向分配内存的指针;如果失败,返回 NULL

realloc(Re-allocation):

示例:

#include<stdlib.h>
void Test()
{int* p1 = (int*)calloc(4,sizeof(int));if (p1 == NULL){perror("calloc");exit(EXIT_FAILURE);}int* p2 = (int*)calloc(p1, sizeof(int)*10);if (p1 == NULL){perror("malloc");exit(EXIT_FAILURE);}free(p2);
}
  • 原型:void *realloc(void *ptr, size_t new_size);

  • 功能:用于更改先前分配的内存块的大小。

  • 参数:ptr是之前由 malloc, calloc  realloc 返回的指针;new_size 是新的内存块大小。

  • 返回值:如果分配成功,返回指向重新分配内存的指针;如果失败,返回 NULL。如果 ptr  NULL,则其行为等同于 malloc(new_size)

        以上函数主要要从函数功能进行区分,要注意的是无论是哪一种函数开辟或者扩容的空间,后面都需要手动free进行释放。同时对于realloc我们扩容的空间释放只需要释放后者,他的作用是根据需要重新分配内存,可能会将现有的内存块的内容移动到新的位置,然后释放原来的内存块。如果realloc内部决定在现有块的末尾或相邻位置分配新的内存,它将返回原始块的指针,而不是分配新的块。因此,如果新的内存块被分配在了原来的内存块上,释放原来的内存块也就释放了新的内存块,因为它们实际上是同一块内存。对于用户来说,只需保留realloc返回的指针即可,而不需要显式释放原来的内存块,需要避免多次释放。

内存泄露  

        内存泄漏是指在程序运行时,动态分配的内存没有被释放,导致系统无法再次使用这些内存块。当一个程序在运行过程中分配了内存,但在不再需要这些内存时没有释放,就会发生内存泄漏。这对一个操作系统或者服务器的话影响是非常巨大的。

        内存泄漏可能导致程序运行时占用的内存不断增加,最终耗尽系统的可用内存,从而导致程序性能下降或崩溃。内存泄漏是一种常见的编程错误,因此在开发过程中应该特别注意及时释放不再需要的内存。就比如我们经常使用一个软件的时候,长时间运转我们会发现程序消耗的内存越来越大,手机或者电脑越来越卡顿,这可能就存在内存泄露的问题,内存不能及时释放,只能新开辟空间用于后续进程,反复叠加下对性能的消耗会越来越大。合理的掌握内存分配和管理,能极大的提高程序运行的效率进而提高用户使用的体验。

        因此无论我们是使用方式的什么开辟的空间,动态内存开辟的空间需要程序员自己去掌控,要及时的free开辟的空间。

C++的内存管理方式

        在C++中,由于C++向下兼容,虽然你仍然可以使用malloc和free,但是推荐使用newdelete。首先我们知道,C++是一门面向对象的语言,和C面向过程不同,我们要体现C++的优越性就一定要对底层问题的处理上进行封装调用。

        malloc很显然能完成对于内存开辟问题,但是在C++中,我们通常是构建一个自定义的类,而构建一个类后我们需要完成对类的初始化。在使用malloc的时候我们能清晰的感觉到,首先对void*的返回值我们要进行强制类型转换,并且还需要传递空间内存大小完成开辟的工作。然后我们还需要手动调用构造函数进行构造么,这也太麻烦了。

        因此对于这些地方使用极为不便和无能无力我们就需要使用新的操作方式。

new/delete

        在C++中,new和delete是用于动态内存管理操作符,它们分别用于在堆上分配和释放内存。这两个操作符提供了对动态内存的灵活控制,特别适用于需要在运行时确定内存大小的情况。

 new的基本用法

        new用于在堆上动态分配内存,并返回指向新分配内存的指针。它同时会调用对象的构造函数(如果有的话),对新分配的内存进行初始化。(一个对象就轻松被new出来咯!!!)

void Test()
{//动态申请一个int类型的空间int* ptr1 = new int;//动态申请一个int类型的空间并初始化为10int* ptr2 = new int(10);//动态申请10个int类型的空间int* ptr3 = new int[10];
}

delete的基本用法

delete用于释放通过new分配的内存。在释放内存之前,delete会调用对象的析构函数(如果有的话),对内存中的对象进行清理工作。 (一个对象的终点)

void Test()
{//动态申请一个int类型的空间int* ptr1 = new int;//动态申请一个int类型的空间并初始化为10int* ptr2 = new int(10);//动态申请10个int类型的空间int* ptr3 = new int[10];delete ptr1;//释放单个对象的空间delete ptr2;delete[] ptr3;//释放数组空间
}

注意:要时刻注意一一对应

  • 对于每个new,应该有一个相应的delete,对于每个new[],应该有一个相应的delete[]
  • 不要混合使用newdeletemallocfree,也不要混合使用new[]和delete[]mallocfree
  • 在使用newnew[]时,应该使用相应的deletedelete[]来释放内存,以确保正确调用析构函数。

malloc/free和new/delete的区别

        我们知道,malloc并不会去调用构造函数,free也不会去调用析构函数。对于自定义类型的空间处理上new和delete不仅能完成空间的开辟还能完成调用构造函数和析构函数。对于自定义类来讲显然new和delete更甚一筹,而对于非自定义类型的时候,两者并无太大区别,不过new和delete在使用方式上也相对更加简略了。

class A
{
public:A(int date = 0){_date = date;printf("a()  ");printf("_date=%d\n", _date);}~A(){printf("~a()\n");}
private:int _date;
};
int main()
{printf("____________a__________________\n");A* a = new A;delete a;printf("____________b__________________\n");A* b = new A(10);delete b;printf("____________c__________________\n");A* c = new A[10];delete[] c;printf("____________d__________________\n");A* d = new A[5]{ 1,2,3,4 };delete[] d;return 0;
}

在内存管理上来讲当分配内存失败的时候两者也有区别

  • 在分配内存失败时,malloc返回 NULL,需要手动检查。
  • new运算符在分配内存失败时抛出异常,可以通过异常处理机制来处理。

operator new与operator delete函数

     在 C++ 中,new是一个关键字,它用于动态分配内存并构造对象,而operator new是与new相关的一个全局函数,用于执行实际的内存分配。delete同理。我们这里进一步剖析new和delete的底层原理。

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

        operator new与operator delete是两个全局函数,在上述代码中是其函数的实现原理,我们不难发现,其函数实际上也是通过malloc来申请空间的,成功申请后就直接返回,否则执行用户提供的空间应对不足的措施,如果用户继续申请就会抛异常。operator最终也是通过调用free来释放空间的。

        抛异常是c++中一种在程序执行过程中遇到错误或异常情况时,通过特殊的语句将程序的控制权传递给异常处理机制。这个过程通常称为"抛出异常"。异常是一种表示程序错误或不正常情况的机制。当某个异常条件发生时,程序会停止当前执行路径,然后查找能够处理这个异常的代码块。如果找到了匹配的异常处理代码块,程序会跳转到该处进行处理。如果找不到对应的处理代码,程序可能会终止运行。(后面会详细讲解)

        在main函数中,我们使用try块来包裹可能抛出异常的代码,然后使用catch块来捕获并处理异常。如果异常被抛出,控制流将跳转到匹配的catch块。

        这里我们演示一下抛异常和捕获异常:

#include <iostream>
using namespace std;int main(void)
{char* p2 = nullptr;try {char* p2 = new char[1024u * 1024u * 1024u * 2u - 1];}catch (const exception& e) {cout << e.what() << endl;}printf("%p\n", p2);return 0;
}

        在上述演示中,我们申请了大面积的内存空间,程序并没有出现C语言的崩溃现象,而是以抛异常的方式优雅的退出并展示了错误原因。

重载operator new和operator delete(了解)

        一般情况下,不对这两个函数进行重载,但是在一些特定的情况下,我们可以完成一些特殊的请求。比如在申请和释放空间的时候有一些特殊的需求,打印一些日志信息,帮助用户检测是否存在内存泄露的情况之类的场景需求。

void* operator new(std::size_t size) 
{void* ptr = std::malloc(size);std::cout << "自定义new分配了 " << size << " 字节的内存,其地址空间为 " << ptr << std::endl;return ptr;
}void operator delete(void* ptr) noexcept 
{std::cout << "自定义 delete:释放了地址为 " << ptr << " 的内存" << std::endl;std::free(ptr);
}int main() 
{int* p = new int;delete p;int* arr = new int[5];delete[] arr;return 0;
}

new和delete的底层原理

new的底层原理:

  1. 计算分配的总内存大小:new首先计算要分配的总内存大小,包括对象的大小以及额外的管理信息。

  2. 调用operator new分配内存:new调用operator new函数进行实际的内存分配。可以重载它以提供自定义的分配逻辑。

  3. 调用对象的构造函数: 如果new用于分配单个对象,它会调用对象的构造函数,完成对象的初始化。

  4. 返回分配的内存地址: 最后,new返回指向新分配对象。

delete的底层原理:

  1. 调用对象的析构函数: 在使用delete释放对象之前,会调用对象的析构函数,进行对象资源的清理。

  2. 调用operator delete释放内存:delete调用operator delete函数释放之前分配的内存。可以重载它以提供自定义的释放逻辑。

  3. 返回释放的内存: 最终,操作系统可以重新利用被释放的内存。

数组的原理即是调用相应的operator new[]对多个对象的空间申请和构造,销毁同理。

定位new表达式

定位new表达式是在一个已经分配了原始的内存空间中显示的调用构造函数完成对对象的初始化操作。

new (ptr) Type(init);

其中,prt是指向已分配内存的指针,Type是对象的类型,init是可选的初始化参数(可以带参也可以不带参)。

这种形式的new主要用于在预分配的内存区域中创建对象,配合内存池使用,因为内存池分配的内存没有初始化。而不是在堆上分配新的内存。这对于一些特殊的需求,如实现自定义内存池或在固定地址处构造对象等情况非常有用。

手动调用析构函数: 当你不再需要这个对象时,需要手动调用析构函数来释放资源

int main()
{// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行A* p1 = (A*)malloc(sizeof(A));new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参p1->~A();free(p1);A* p2 = (A*)operator new(sizeof(A));new(p2)A(10);p2->~A();//可以显示的调用析构函数,构造函数不可以。operator delete(p2);return 0;
}

总结

        本章主要从C/C++的不同内存管理机制入手,深入浅出的讲解new和delete的底层原理和一些扩展知识。对于文章中涉及到内存池和抛异常等机制我们会在后续单独讲解,本章点到为止。希望能对大家深入理解new和delete有所帮助!


    作者水平有限,如有错误欢迎指正!


    

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

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

相关文章

Git 命令详解

系列文章目录 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 C技能系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the future of dream we…

【Python】Python爬虫使用代理IP的实现

前言 在爬虫的过程中&#xff0c;我们经常会遇到需要使用代理IP的情况。比如&#xff0c;针对目标网站的反爬机制&#xff0c;需要通过使用代理IP来规避风险。因此&#xff0c;本文主要介绍如何在Python爬虫中使用代理IP。 一、代理IP的作用 代理IP&#xff0c;顾名思义&…

JVM虚拟机:垃圾回收器之Parallel Old(老年代)

本文重点 本文将学习老年代的另外一种垃圾回收器Parallel Old(PO)&#xff0c;这是一种用于老年代的并行化垃圾回收器&#xff0c;它使用标记整理算法进行垃圾回收。 历史 在1.6之前&#xff0c;新生代使用Parallel Scavenge只能搭配老年代的Serial Old收集器&#xff0c;而…

Ubuntu22.04 下 NFS 相关问题与完整配置(客户机 MacOS)

categories: [Linux-Shell] tags: Linux NFS 写在前面 最近折腾一下 NFS, 先白嫖一顿华子云的 1 个月服务器, 2C4G 感觉不错了, 但NFS 配置起来还是有点难度, 主要还是随机分配的端口配置方面比较恶心. server环境: 华为云 2C4G Ubuntu22.04 client环境: MacOS M1 with brew …

小程序多文件上传 Tdesign

众所周知&#xff0c;小程序文件上传还是有点麻烦的&#xff0c;其实主要还是小程序对的接口有诸多的不便&#xff0c;比如说&#xff0c;文件不能批量提交&#xff0c;只能一个个的提交&#xff0c;小程序的上传需要专门的接口。 普通的小程序的页面也比普通的HTML复杂很多 现…

Java,多线程,线程安全的懒汉式、死锁、ReentrantLock的使用以及一些知识点补充

关于线程安全地懒汉式有以下几种方式&#xff1a; /*** 实现线程安全的懒汉式*/ public class BankTest {Bank b1 null;Bank b2 null;public static void main(String[] args){BankTest bb new BankTest();Thread t1 new Thread(){Overridepublic void run(){bb.b1 Bank.…

安卓RadioButton设置图片大小

RadioButton都不陌生&#xff0c;一般我们都会设置图片在里面&#xff0c;这就涉及一个问题&#xff0c;图片的大小。如果图片过大&#xff0c;效果很不理想。搜了很多方法&#xff0c;都不理想。无奈只能自己研究了 代码如下&#xff1a; 1&#xff0c;一个简单的 RadioButt…

IBM Qiskit量子机器学习速成(一)

声明&#xff1a;本篇笔记基于IBM Qiskit量子机器学习教程的第一节&#xff0c;中文版译文详见&#xff1a;https://blog.csdn.net/qq_33943772/article/details/129860346?spm1001.2014.3001.5501 概述 首先导入关键的包 from qiskit import QuantumCircuit from qiskit.u…

力扣138:随机链表的复制

力扣138&#xff1a;随机链表的复制 题目描述&#xff1a; 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff…

《网络协议》02. 物理层 · 数据链路层 · 网络层

title: 《网络协议》02. 物理层 数据链路层 网络层 date: 2022-08-31 22:26:48 updated: 2023-11-08 06:58:52 categories: 学习记录&#xff1a;网络协议 excerpt: 物理层&#xff08;数据通信模型&#xff0c;信道&#xff09;、数据链路层&#xff08;封装成帧&#xff0c…

NtripShare Mos地铁自动化监测终端盒子硬件设计

自动化监测产品到目前为止做了接近一年&#xff0c;在软件层面上&#xff0c;控制终端软件、平台软件、网平差算法都已解决&#xff0c;硬件盒子始终是心里过不去的坎&#xff0c;最终还是没有耐住性子自己做了一把。 选型如下&#xff1a; 1、主板:瑞芯微RK3568主板。 2、外…

向量的点积和外积

参考&#xff1a;https://www.cnblogs.com/gxcdream/p/7597865.html 一、向量的内积&#xff08;点乘&#xff09; 定义&#xff1a; 两个向量a与b的内积为 ab |a||b|cos∠(a, b)&#xff0c;特别地&#xff0c;0a a0 0&#xff1b;若a&#xff0c;b是非零向量&#xff0c;…

【性能测试】服务端中间件docker常用命令解析整理(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、搜索 docker …

11-08 周三 图解机器学习之实现逻辑异或,理解输出层误差和隐藏层误差项和动量因子

11-08 周三 图解机器学习之实现逻辑异或&#xff0c;理解输出层误差和隐藏层误差项 时间版本修改人描述2023年11月8日14:36:36V0.1宋全恒新建文档 简介 最近笔者完成了《图解机器学习》这本书的阅读&#xff0c;由于最近深度学习网络大行其是&#xff0c;所以也想要好好的弄清…

Effective C++ 系列和 C++ Core Guidelines 如何选择?

Effective C 系列和 C Core Guidelines 如何选择&#xff1f; 如果一定要二选一&#xff0c;我会选择C Core Guidelines。因为它是开源的&#xff0c;有300多个贡献者&#xff0c;而且还在不断更新&#xff0c;意味着它归纳总结了最新的C实践经验。最近很多小伙伴找我&#xff…

基于springboot实现智慧外贸平台系统【项目源码+论文说明】计算机毕业设计

基于springboot实现智慧外贸平台系统演示 摘要 网络的广泛应用给生活带来了十分的便利。所以把智慧外贸管理与现在网络相结合&#xff0c;利用java技术建设智慧外贸平台&#xff0c;实现智慧外贸的信息化。则对于进一步提高智慧外贸管理发展&#xff0c;丰富智慧外贸管理经验能…

Java进阶篇--Executors类创建常见线程池

目录 线程池架构 newSingleThreadExecutor newFixedThreadPool newCachedThreadPool newScheduledThreadPool Executors和ThreaPoolExecutor创建线程池的区别 两种提交任务的方法 线程池架构 线程池是一种线程管理的机制&#xff0c;用于维护和复用线程&#xff0c;以…

Leetcode2834. 找出美丽数组的最小和

Every day a Leetcode 题目来源&#xff1a;2834. 找出美丽数组的最小和 解法1&#xff1a;贪心 从最小正整数 1 开始枚举&#xff0c;设当前数为 num&#xff0c;如果 nums 里没有 target - num&#xff0c;就说明可以添加 num&#xff0c;依次填满直到有 n 个数即可。 用…

【k8s-1】基于docker Desktop一键式搭建k8s环境

在docker desktop中一键启动k8s环境很简单。 下面介绍如何启动dashboard&#xff0c;dashboard仪表盘是新手学习k8s至关重要的一个工具。 1、配置控制台 kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml 2、开…

k8s的安装部署,详细过程展示(保姆级安装教程)

k8s应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点&#xff1a;不能为应用程序定义资源使用…