动态内存分配
使用malloc类的函数分配内存
用这些内存支持应用程序
用free函数释放内存
实例代码
int *ptr = (int *)malloc(sizeof(int));//动态分配内存,大小为一个int
*pi = 5;
cout << *pi << endl;
free(pi);//释放内存
图片说明 (主函数也是函数,所以在栈里创建了指针,并指向在堆中开辟的内存)
内存泄漏
如果不再使用已分配的内存却没有将其释放将导致内存泄漏
丢失内存地址
没有调用free函数
丢失地址
int main()
{
int *pi = (int *)malloc(sizeof(int));
*pi = 5;
pi = (int *)malloc(sizeof(int));//又在堆区开辟了空间,并让指针指向它
//这时候,我们无法找到第一次堆区开辟的空间了,我们也无法释放它了,这造成了内存泄漏
return 0;
}
当pi被赋值为一个新地址时,它丢失了原指向的内存地址,第一次分配的地址丢失了
int main()
{
char* name = (char*)malloc(strlen("Susan") + 1);
strcpy_s(name, strlen("Susan") + 1,"Susan");
while (*name != 0) {
cout << *name;
name++;//改变了name的值,找不到原来开辟内存的初始地址
}
free(name);//free之后,显示异常
return 0;
}
我们改变了name的值,而name是一个地址,它现在找不到原来开辟内存的初始地址,我们想要释放掉原来开辟的内存,需要分配内存的起始地址(free之后,显示异常)
如图所示,指针指向了末尾的地址,但我们需要分配内存的起始地址
没有调用free函数
int main()
{
int* ptr = (int*)malloc(sizeof(int));
*ptr = 10;
//free(ptr);注释掉了free函数
return 0;
}
注释掉了free函数,编译器不会主动释放我们所开辟的内存
动态内存分配函数
各类动态内存分配函数的作用
malloc函数
int *ptr = (int *)malloc(sizeof(int));//动态分配内存,大小为一个int
malloc的操作
- 从堆上分配内存
- 内存不会被修改或是清空
- 返回首字节的地址
最好加上强制类型转换
- 便于理解函数的用意
- 代码可以和C++兼容,它需要显示的强制类型转换
分配内存失败
- 如果声明一个指针,但没有在使用之前为它指向的地址分配内存,那么内存通常包含垃圾,这往往会导致一个无效内存引用的错误
int *p;
...
cout << *p << endl;
如代码所示,我们只是声明了一个指针,却没有为它初始化,不知道它指向何处。而我们使用malloc函数是会为指针返回我们所开辟的内存的首地址的
如图所示,栈内的指针不知其指向何处
没有给malloc传递正确的参数
- 申请空间的时候要准确说明,这里推荐使用sizeof操作符,因为每个系统的某些类型的字节数可能不一样,手动计算大小容易分配错误。明确指出类型避免出错
double* ptr = (double*)malloc(10 * sizeof(double));
申请十个double类型大小的存储空间
calloc函数
原型
void *calloc(size_t numElements, size_t elementSize);
calloc函数在分配的同时会清空内存(将其设置为二进制0)
该函数根据两个参数的乘积分配内存,无法分配返回空指针
int *ptr = (int *)calloc(5,sizeof(int));// 5 * 4 = 20 (字节)
等价于
int main() {
int *ptr = (int *)malloc(sizeof(int));
memset(ptr,0,5*sizeof(int));
for (int i=0; i<5; i++)
cout << *(ptr+i) << " "; // 0 0 0 0 0
return 0;
}
realloc函数
我们可能时不时需要增加或减少为指针分配的内存,realloc函数会重新分配内存
原型
void *realloc(void *ptr, size_t size);
该函数接受两个参数,第一个是指向原内存块的指针,第二个是请求的大小。会重新找一个更大的块复制原来内存块的数据,再返回新块的指针
realloc函数的行为
案例(比原内存块小)
int main()
{
char* string1;
char* string2;
string1 = (char*)malloc(16);
strcpy(string1, "0123456789AB");
string2 = (char *)realloc(string1, 8);//分配更小的空间
printf("string1 Value: %p [%s]\n", string1, string1);
printf("string2 Value: %p [%s]\n", string2, string2);
return 0;
}
输出
string1 Value: 0000000000ae1510 [0123456789AB]
string2 Value: 0000000000ae1510 [0123456789AB]
它没有选择新的空间,而是在原始的内存块,但是我们限制了8个字节,却可以继续访问到后面的数据,或许我们可以将NULL赋值给后面的地址以停止访问。
以上是内存的分配情况,我们虽然指定了8个字节的限制,但是因为我们没有找新的内存块,用的是以往的,所以哪怕是越界却依旧可以访问后面的字符
alloca函数和变长数组
alloca函数在函数的栈帧上分配内存,函数返回后会自动释放内存。若底层的运行时系统不急于栈,alloca函数会很难实现,所以这个函数是不标准的,避免使用。
C99引入了变长数组(VLA),允许函数内部生命和创建其长度由变量决定的数组
void array(int n)
{
char* buffer[n];
}
用free函数释放内存
int main()
{
int* ptr = (int*)malloc(sizeof(int));
*ptr = 10;
cout << *ptr << endl;
free(ptr);
return 0;
}
free释放指针参数所指向的内存,这块内存被返还给堆。尽管指针仍然指向这块区域,但是我们应该将它看作只想垃圾数据
迷途指针也即我们所说的悬空指针,为了安全起见,一般要把该指针赋为NULL
将已释放的指针赋值为NULL
已释放的指针仍然可能造成问题,如果我们试图引用一个已释放的指针,其行为将是未定义的
ptr = NULL;
重复释放
如果我们试图重复释放同一块内存,会发生异常
C++深拷贝浅拷贝大家应该都有印象
迷途指针(悬空指针)
如果内存已经释放,而指针还在引用原始内存,这样的指针称为迷途指针(悬空指针),迷途指针没有指向有效对象,也被称为过早释放
造成问题
- 如果访问内存,则行为不可预期
- 如果内存不可访问,则是段错误
- 潜在的安全隐患
一般犯错情况
- 访问已经释放的内存
- 返回的指针指向的是上次函数调用中的自动变量
迷途指针案例
int main()
{
int* ptr = (int*)malloc(sizeof(int));
*ptr = 10;
cout << *ptr << endl;
free(ptr);
return 0;
}
此时的ptr就是迷途指针,如果我们使用指针对此内存块进行访问,将会出现不可预估的后果
int main()
{
int* ptr = (int*)malloc(sizeof(int));
int* ptr2 = ptr;
*ptr = 10;
cout << *ptr << endl;
free(ptr);
return 0;
}
此时ptr2是ptr的指针别名,它也是迷途指针,我们经常赋值好几个指针相同的地址,这很容易造成许多的迷途指针
处理迷途指针
释放指针后,将其置为NULL,后续使用这个指针会终止程序,但是赋值只影响一个迷途指针,如果存在多个指针公用的情况,很难处理
写一个特殊的函数代替free函数
有些系统会在释放后覆写数据,在不抛出异常的情况下,程序员看到这些值就会明白程序可能在访问已释放的内存
用第三方工具检测迷途指针和其他问题
动态分配内存技术
C的垃圾回收
有些非标准的技术可以用来解决部分问题i,可以自动的释放内存。内存不再使用之后会被收集起来以备后续使用,释放的内存成为垃圾,所以称为垃圾回收
垃圾回收意义
- 程序员不需要想着何时释放内存
资源获取及初始化
可以用来解决C++中资源的分配和释放,即使有异常发生,这种技术也可以保证资源的初始化和后续的释放。
使用异常处理函数
不是很懂。。。