您的位置:首页 > 游戏 > 游戏 > 【C语言】动态内存管理

【C语言】动态内存管理

2024/10/30 23:09:20 来源:https://blog.csdn.net/weixin_74076508/article/details/141000417  浏览:    关键词:【C语言】动态内存管理

1. 为什么要有动态内存分配

相信同学们通过前面的学习知道,在C语言中有以下的内存开辟⽅式:

比如:①创建一个变量

int a = 10;//在栈空间上申请四个字节的空间

创建一个数组

②一个数组是一块连续的内存空间

int arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的⽅式有两个特点:

• 空间开辟大小是固定的

• 数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整

举一个具体的例子: 

int arr[100] = {0};

这里给了我们一个能够存放100个整型的连续内存空间,那么如果我们有50个整型类型的数据要储存,那么就要浪费剩余的内存空间,如果我们有200个整型类型的数据要存储,那么arr数组的空间又不够我们存放数据

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小

在程序运⾏的时候才能知道,那数组的编译时开辟空间的⽅式就不能满⾜了。

于是C语言引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。

那么本期博客就给同学们学习在动态开辟上所用到的几个函数

2. malloc和free

2.1 malloc

C语⾔提供了⼀个动态内存开辟的函数:

函数原型:malloc - C++ Reference (cplusplus.com)

void* malloc (size_t size);

参数: 

size -- 内存块的大小,以字节为单位,类型是无符号整型(size_t)

功能:这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。 

对于返回类型也做了介绍:

 从函数原型也能看出:返回值的类型是 void* ,

所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃⼰来决定。

另外如果malloc函数开辟失败的话,则返回⼀个 NULL 指针。

因此malloc的返回值⼀定要做检查!
 

另外注意:如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。

2.2 free

C语言提供了另外⼀个函数free,专门是⽤来做动态内存的释放和回收的,函数原型如下:
free - C++ Reference (cplusplus.com)

void free (void* ptr);//传过去是要释放的空间的起始地址

free函数⽤来释放动态开辟的内存。

• 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。

• 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在 stdlib.h 头⽂件中。

这里补充一点:

如果malloc函数开辟失败的话,我们特别想知道malloc开辟失败的原因是什么,

我们可以使用库函数 perror ,这个函数会直接打印出malloc开辟失败所对应错误信息。

我们一起看一下这个函数原型:perror - C++ Reference (cplusplus.com)

void perror ( const char * str );

 功能:打印错误信息

参数:

str -- 是一个字符串,包含了一个自定义消息,将显示在原本的错误消息之前。

返回值:无返回值 

举个例⼦:

我们来看看malloc 开辟空间的具体使用方法:

#include <stdio.h>
#include <stdlib.h>int main()
{//开辟内存int* ptr = (int*)malloc(40);int* p = ptr;if (p == NULL){perror("malloc");return 1;}//内存操作int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}//打印for (i = 0; i < 10; i++){printf("%d ", *(p + i));}//内存释放free(ptr);ptr = NULL;return 0;
}

代码解释如下:

#include <stdio.h>
#include <stdlib.h>int main()
{int* ptr = (int*)malloc(40);//malloc 函数会返回所申请的内存块的起始地址,返回类型为void*//将其强制类型转换为int*意思是所申请的这块空间之后将以整数对其进行访问操作int* p = ptr;//因为malloc要返回申请空间的起始地址,所以通常不直接对ptr操作,而是另外创建//一个指针存放ptr,后续对p进行操作if (p == NULL){//malloc函数在申请内存空间时,若空间大小不够,会返回一个空指针//此时内存空间就会申请失败perror("malloc:");//打印程序错误信息,双引号内的内容由自己定//这里perror函数刚好可以验证空间是否申请成功return 1;//主函数中return 1表示程序异常结束}//代码走到这里,说明malloc成功开辟空间,我们在空间里赋值,//通过循环将 0 到 9 依次存储到分配的内存空间中。//这里使用了我们学习过指针偏移的方式 *(p + i) 来进行赋值。int i = 0;for (i = 0; i < 10; i++)//申请的空间大小为40个字节,转换为整形即10个整形{*(p + i) = i;}//同样通过指针偏移的方式读取并打印出存储在内存中的值。for (i = 0; i < 10; i++){printf("%d ", *(p + i));}free(ptr);//申请的这块内存空间使用结束后,需要释放这片空间,free函数的参数为申请空间的起始地址ptr = NULL;//空间释放后,ptr仍然指向空间内的一个有效地址,为避免后续对其使用造成野指针的情况return 0;
}

运行结果:

3. calloc

C语⾔还提供了⼀个函数叫 calloc , calloc 函数也⽤来动态内存分配。原型如下:

calloc - C++ Reference (cplusplus.com) 

void* calloc (size_t num, size_t size);

从这里我们看出:

• 这个函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

举个例子:

#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)calloc(10, sizeof(int));//calloc函数与malloc一样都是用于申请内存空间的,不同之处在于,calloc会对申请的空间进行初始化//申请的空间会被全部初始化为0,其余地方的使用与malloc一样if (p == NULL){perror("calloc:");return 1;}for (int i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}

 运行结果:

4.realloc(内存扩容)

realloc函数的出现让动态内存管理更加灵活。
有时我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。realloc 函数就可以做到对动态开辟内存大小的调整。

 函数原型如下:

realloc - C++ Reference (cplusplus.com)

void* realloc (void* ptr, size_t size);

• ptr 是要调整的内存空间的起始地址

• size 调整之后新⼤⼩

• 返回值为调整之后的内存空间的起始位置。

• 这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。realloc函数在调整空间时存在两种情况:

  • 情况一:原有空间后有足够大的空间
  • 情况二:原有空间后没有足够大的空间

情况1

当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。

情况2

当是情况2 的时候,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。

 由于上述的两种情况,因此我们在使用realloc函数时需要多加注意。

举个例子:

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc:");return 1;}int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}//空间不够,希望能放20个元素,考虑扩容int* ptr = (int*)realloc(p, 80);//realloc函数为内存扩容函数//因为realloc也会扩容失败,当扩容失败时,realloc会返回一个空指针//如果直接将realloc的返回值给之前开辟空间时的p,那么当返回值为NULL,即扩容失败时,//这时候p被赋值为空指针,原来那片开辟的空间就会丢失,造成内存泄漏if (ptr != NULL){p = ptr;}//扩容成功,开始使用//不再使用,就释放free(p);p = NULL;return 0;
}

5. 常⻅的动态内存的错误

5.1 对NULL指针的解引⽤操作

举个例子:

大家看看下面这个代码,大家认为这个程序有没有问题? 

#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)malloc(INT_MAX);*p = 20;free(p);
}

当我们调试发现,程序会崩溃。

 原因是这样的:

首先,使用 malloc(INT_MAX) 来分配内存是一种不太常见且不太安全的做法。INT_MAX 通常是一个非常大的值,可能会超出系统的可用内存,导致分配失败。

在执行 *p = 20; 这一行时,如果 p 的值是 NULL (即内存分配失败),那么这将导致未定义的行为,可能会引发程序崩溃、数据损坏或其他难以预测的错误。

例如,如果系统内存不足,malloc 可能无法成功分配这么大的内存空间,此时 p 就会是 NULL 。当尝试对 NULL 指针进行解引用并赋值时,就像上述代码中那样,很可能导致程序异常终止。

另外,即使内存分配成功,在使用完后通过 free(p); 释放了分配的内存,以避免内存泄漏。但要确保在释放之前对内存的操作都是合法和正确的,避免因错误的操作导致程序出现问题。

总之,在实际编程中,应根据实际需求合理地分配内存大小,并妥善处理内存分配可能失败的情况。

 5.2 对动态开辟空间的越界访问

void test()
{int* p = (int*)malloc(10 * sizeof(int));if (p != NULL){for (int i = 0; i <= 10; i++){*(p + i) = i + 1;  //当i是10的时候越界访问}}free(p);p = NULL;
}

这段代码存在问题。

在循环中:

for (int i = 0; i <= 10; i++)
{*(p + i) = i + 1;
}

由于分配的内存大小为 10 * sizeof(int) ,所以有效的索引范围应该是 0 到 9 。当 i 等于 10 时,对 *(p + 10) 进行赋值会导致越界访问。

5.3 对非动态开辟内存使用free释放

void test()
{int a = 10;int *p = &a;free(p);//ok?
}

在这段代码中,free(p) 是不正确的操作。

p 指向的是一个普通的局部变量 a ,而不是通过 malloccalloc 或 realloc 等动态内存分配函数分配的内存空间。

对非动态分配的内存使用 free 会导致未定义的行为,可能会使程序崩溃或产生不可预测的结果。

例如,程序可能会在执行这一行时突然终止,或者后续的代码执行出现异常。

正确的做法是只对通过动态内存分配获取的指针使用 free 进行内存释放。

 5.4 使⽤free释放⼀块动态开辟内存的⼀部分

int main()
{int* p = (int*)malloc(100);  // 申请100个字节大小的空间if (p == NULL){return 1;}for (int i = 0; i < 10; i++){*p = i + 1;p++;  }free(p);p = NULL;return 0;
}

这段代码存在错误。

在循环中使用 p++ 会导致 p 不再指向最初动态分配的内存的起始位置。当执行 free(p) 时,由于 p 已经不再指向正确的起始位置,这将导致未定义的行为,可能会引发程序错误甚至崩溃。

5.5 对同⼀块动态内存多次释放

void test()
{int *p = (int *)malloc(100);free(p);free(p);
}

 

这段代码存在错误。

在 test 函数中,对已经释放的内存指针 p 再次调用 free 函数,这是不合法的操作。

当第一次调用 free(p) 时,所分配的内存已经被归还给系统,p 所指向的内存不再有效。再次调用 free(p) 会导致未定义的行为,可能会使程序崩溃或产生不可预期的结果。

例如,可能会导致内存管理的混乱,影响到其他正在使用的内存区域,或者在某些情况下导致程序直接异常终止。

为了避免这种错误,应该确保对同一个动态分配的内存指针只调用一次 free 函数。

如下面这个代码:

void test()
{int *p = (int *)malloc(100);free(p);free(p);
}

这段代码存在错误。

在 test 函数中,对已经释放的内存指针 p 再次调用 free 函数,这是不合法的操作。

当第一次调用 free(p) 时,所分配的内存已经被归还给系统,p 所指向的内存不再有效。再次调用 free(p) 会导致未定义的行为,可能会使程序崩溃或产生不可预期的结果。

例如,可能会导致内存管理的混乱,影响到其他正在使用的内存区域,或者在某些情况下导致程序直接异常终止。

为了避免这种错误,应该确保对同一个动态分配的内存指针只调用一次 free 函数。

 5.6 动态开辟内存忘记释放(内存泄漏)

void test()
{int *p = (int *)malloc(100);if(NULL != p){*p = 20;}
} int main()
{test();while(1);
}

这段代码存在潜在的问题。

在 test 函数中,虽然分配了 100 个字节的内存,但并没有指定这 100 个字节如何使用。

直接使用 *p = 20; 只是给 p 所指向的内存的第一个 int 大小的位置赋值为 20 。

如果后续的代码期望按照特定的方式使用这 100 个字节,例如将其当作一个包含多个 int 元素的数组,那么这种简单的赋值可能无法满足需求。

此外,如果在后续的代码中不再对这块内存进行其他操作,并且在程序结束时也没有使用 free(p) 释放内存,就会导致内存泄漏。

⚠️ 忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。

切记:动态开辟的空间⼀定要释放,并且正确释放。

6. 动态内存经典笔试题分析

6.1 题⽬1:

void GetMemory(char* p)
{p = (char*)malloc(100);
} void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}int main()
{Test();return 0;
}

请问运⾏Test 函数会有什么样的结果?

我们运行结果发现,最终程序崩溃了
 

我们分析一下这个代码究竟出现了什么问题了

  1. 内存非法访问:我们知道传值调用时,形参只是实参的临时拷贝,对形参的改变无法影响实参,这时str仍是空指针,而strcpy拷贝会对空指针进行解引用操作,对NULL指针解引用会出错!

 

 

2. 内存泄漏:在GetMemory()函数内部动态申请了100字节的空间,因为p随着函数结束而被销毁,所以已经再也找不到该空间,会造成内存泄漏。

 改正方法:

  1. 我们要想改变str就需要传址调用,而str本身就是个指针变量,传指针变量的地址需要二级指针来接收
  2. 使用完之后必须释放内存。
void GetMemory(char** p)
{*p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);// 释放free(str);str = NULL;
}

6.2 题目二

#include <stdio.h>
#include <stdlib.h>char* GetMemory()
{char p[] = "hello world";return p;
}void test()
{char* str = NULL;str = GetMemory();printf(str);
}int main()
{test();return 0;
}

大家可以思考一下,上述代码有没有问题?

运行结果,发现打印的是这样乱码的结果

同学们会感到很疑惑,奇怪,为什么是这样的结果呢?

别急,我们来分析一下:

 

 

针对上述问题,我们可以对代码进行如下修改:

① 返回 p ,让 str 接收:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 修改返回类型为char*
char* GetMemory(char *p)
{p = (char*)malloc(100);return p; // 将p带回来
}void Test()
{char *str = NULL;str = GetMemory(str); // 用str接收,此时str指向刚才开辟的空间strcpy(str, "hello world"); // 此时copy就没有问题了printf(str);// 用完之后记得free,就可以解决内存泄露问题free(str);str = NULL; // 还要将str置为空指针
}int main()
{Test();return 0;
}

② 将值传递改为址传递:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>//              用char**接收
void GetMemory(char** p)
{*p = (char*)malloc(100);
}void Test()
{char* str = NULL;GetMemory(&str); // 把str地址传递strcpy(str, "hello world");printf(str);// 记得free释放,就可以避免内存泄露问题free(str);str = NULL; // 还要将str置为空指针
}int main()
{Test();return 0;
}

经过我们的修改,我们运行代码,hello world 就打印出来了

 

6.3 题⽬3:

void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
} void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}int main()
{Test();return 0;
}

 请问运⾏Test 函数会有什么样的结果?

 运行结果,我们发现虽然打印出hello,但是这个代码还是有一点问题的

分析:Test函数里面将str进行传址调用,在GetMemory函数里面申请100个字节大小的空间,将hello拷贝到str所指向的空间中,但是使用之后并没有使用free函数进行释放,导致内存泄漏。
解决:申请的空间使用完之后要使用free函数进行释放,并将str置为空指针

6.4 题⽬4:

void Test(void)
{char *str = (char *) malloc(100);strcpy(str, "hello");free(str);if(str != NULL){strcpy(str, "world");printf(str);}
}

 请问运⾏Test 函数会有什么样的结果?

分析:Test函数里面str申请了100个字节的空间,将hello拷贝到str所指向的空间中,就直接用free释放掉了,导致str成了野指针,之前将hello拷贝到str中,所以str一定不是空指针,

因为 free(str); 之后 str 成为野指针, if(str != NULL) 语句不起作⽤。

6. 柔性数组

同学们可能从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。

C99 中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。

例如:

typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;

有些编译器会报错⽆法编译可以改成:
 

typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;

6.1 柔性数组的特点:

结构中的柔性数组成员前⾯必须⾄少⼀个其他成员

sizeof 返回的这种结构大小不包括柔性数组的内存

包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,

并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

⚠️ps:除了malloc函数,realloc、calloc等动态内存开辟的函数也需要类似的操作

例如:

typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;int main()
{printf("%d\n", sizeof(type_a));return 0;
}

 输出结果是4

说明:sizeof 返回的这种结构大小不包括柔性数组的大小

6.2 柔性数组的使⽤

比如说我现在要数组a里面有10个元素,现在进行malloc一下
示例如下: 

#include<string.h>
#include<errno.h>
struct st_type
{int i;//4字节int a[0];//柔性数组成员,也可以写int a[];
};
int main()
{//假设我现在需要a里有10个元素struct st_type*ps=(struct st_type*)malloc(sizeof(struct st_type) + 10 * sizeof(int));if (ps == NULL)//由于空间可能不够开辟导致malloc开辟失败,开辟失败会返回空指针{printf("%s\n", strerror(errno));return -1;//程序出问题后,跳出程序}//开辟成功int j = 0;for (j = 0;j < 10;j++){ps->a[j] = j;}for (j = 0;j < 10;j++){printf("%d ", ps->a[j]);//打印0-9}printf("\n");//如果想继续用柔性数组a进行打印//比如现在a里只有10个元素,我用完10个了,我还要继续来10个,用realloc追加struct st_type*ptr=realloc(ps, sizeof(struct st_type) + 20 * sizeof(int));//ps:realloc第二个参数是调整后的整体大小if (ptr == NULL){printf("扩容失败\n");return -1;}else{ps = ptr;}//扩容成功int k = 0;for (k = 10;k < 20;k++){ps->a[k] = k;}for (j = 0;j < 20;j++){printf("%d ", ps->a[j]);//打印0-19}//释放空间free(ps);ps = NULL;return 0;
}

 

我们这里需要数组a里有10个元素,那我们malloc的时候要对结构体里的整形i先开辟4个字节,然后为整形数组a再开辟40个字节,然后malloc函数返回开辟空间的起始地址,赋给truct st_type * 类型的ps指针。

malloc(sizeof(struct st_type) + 10 * sizeof(int))这个操作等价于struct st_type类型创建一个变量所占空间,只不过是用malloc来开辟

你改变数组a大小,追加空间时,realloc(ps, sizeof(struct st_type) + 20 * sizeof(int)),realloc的第一个参数仍然是ps,因为你当时是用malloc一次开辟出的一块空间,你是不能单独调整数组a的空间的

6.3 柔性数组的优点

柔性数组就是对一块空间实现动态开辟嘛,那我们之前也讲过指针来动态内存开辟,我们来看一段代码来对比一下这两种方法:

//用指针也可以做到a指向的空间动态变化
struct st_type
{int i;//4字节int *a;//4字节,这里计算结构体大小恰好是8字节
};
int main()
{struct st_type*ps = (struct st_type*)malloc(sizeof(struct st_type));ps->i = 100;ps->a = (int*)malloc(10 * sizeof(int));//a指向40个字节的空间,该空间由int*进行管理int j = 0;for (j = 0;j < 10;j++){ps->a[j] = j;//a[j]=*(a+j)}for (j = 0;j < 10;j++){printf("%d", ps->a[j]);}//a指向的空间不够了,希望调整大小int *ptr = (int*)realloc(ps->a, 20 * sizeof(int));if (ptr == NULL){printf("扩容失败");return -1;}else{ps->a = ptr;}//使用...//释放free(ps->a);ps->a = NULL;free(ps);ps = NULL;
}

这里需要注意的是,在释放空间时,你要先释放指针a指向的空间,然后释放结构体指针

如上图,我们结构体指针ps开辟一块空间,空间里存放整形i和整形指针a,a又malloc(后续如果需要还可以realloc追加)一块空间,如果你先释放掉ps,a就没了,你就没法找到a指向的那块空间了。 

这里对比柔性数组,柔性数组和上述的指针都可以实现一块空间大小的调整,

但是柔性数组有两个好处:

第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。以上,如果我把结构体的内存及其成员要的内存一次性分配好,并返回给用户一个结构体指针,用户做一次free就可以把所有内存都释放掉,并且不用考虑前面说的释放的顺序。
第二个好处是:加快访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片。

ps:内存碎片如下图

 

橙色部分表示malloc开辟的空间 

操作系统给我们一块内存,我们在进行malloc时,不一定就是一块连着一块的,

上图的空白部分就是内存碎片,有些类似我们在生活裁剪布料时,剪下来的一些剩余的边角料一样

扩展阅读:同学们有兴趣了解柔性数组的更多内容,可以阅读下面这篇文章~

C语言结构体里的成员数组和指针 | 酷 壳 - CoolShell

7. 总结C/C++中程序内存区域划分

C/C++程序,对于内存分配了如下几个区域:

这里我们简单了解一下:

1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。

2. 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅式类似于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码

补充阅读:陈浩大佬在14年发布的一篇关于成员数组的博客 C语言结构体里的成员数组和指针

8. 总结

        内存管理是一项非常重要的任务。动态内存管理是指在程序运行时分配和释放内存的过程。通过动态内存管理,我们可以根据需要分配适当的内存空间,并在不再需要时释放它。这使得程序更加灵活,并能够处理各种大小和形状的数据。

以上就是动态内存管理的所有内容了~~~

如果对你的学习有所帮助,别忘了收藏和点赞,有疑问随时可以在评论区骚扰我呦~

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com