【动态内存错误详解和C的内存分区】

常见的动态内存错误

  • 1.动态内存错误
  • 2.经典案例分析
    • 2.1案例一
      • 2.1.1**问题分析**
      • 2.1.2**修改错误**
    • 2.2案例二
      • 2.2.1 原因分析
      • 2.2.2 解决问题
  • c/c++内存分布
    • 1.2 内存分区简介
      • 1.2.1 栈区(stack)
      • 1.2.2 堆区(heap)
      • 1.2.3 全局(静态)区
      • 1.2.4 常量区
      • 1.2.5 代码区

1.动态内存错误

(1)对NULL指针的解引用操作
(2)对动态开辟空间的越界访问
(3)对非动态开辟内存使用free释放
(4)使用free释放一块动态开辟内存的一部分
(5)对同一块动态内存多次释放
(6)动态开辟内存忘记释放(内存泄漏)

2.经典案例分析

2.1案例一

#include
#include
void getmemory(char* p)
{
	p = (char*)malloc(100);
}
void test(void)
{
	char* str = NULL;
	getmemory(str);
	strcpy(str, "hello");
	printf("%s", str);
}
int main()
{
	test();
	return 0;
}

2.1.1问题分析

上述代码运行失败。
【动态内存错误详解和C的内存分区】_第1张图片

首先回顾一下知识;

值传递:将实参的值复制到形参相应的存储单元中,即形参和实参分别占用不同的存储单元。
地址传递:形参并不存在存储空间,编译系统不为形参数组分配内存。因此在数组名或指针作函数参数时所进行的传送只是地址传送,形参在取得该地址之后,与实参共同拥有一段内存空间,形参的变化也就是实参的变化。

原因是:1.因为这里是值传递,p和str分别占用不同的存储单元,malloc只是给p开辟了空间,而str仍然指向空指针。所以strcpy赋值时失败。(对NULL指针的解引用操作
2.没有free(p);造成内存泄漏。(动态开辟内存忘记释放)
注意:printf(“%s”, str);与printf(str);是一样的。

2.1.2修改错误

#include
#include
void getmemory(char** p)
{
	*p = (char*)malloc(100);
}
void test(void)
{
	char* str = NULL;
	getmemory(&str);
	strcpy(str, "hello");
	printf("%s", str);
	free(str);
	str = NULL;
}
int main()
{
	test();
	return 0;
}

【动态内存错误详解和C的内存分区】_第2张图片
运行成功

  1. 修改为地址传参
    下图是一个指向关系图:
    由图可见:str是一级指针,p是二级指针
    【动态内存错误详解和C的内存分区】_第3张图片
    2.增加了free释放
free(str);
	str = NULL;

2.2案例二

#include
#include
char* getmemory(void)
{
	char p[] = "hello word";
	return p;
}
void test(void)
{
	char* str = NULL;
	str = getmemory();
	printf("%s", str);
	free(str);
	str = NULL;
}
int main()
{
	test();
	return 0;
}

2.2.1 原因分析

原因就出在下面代码上;

char* getmemory(void)
{
	char p[] = "hello word";
	return p;
}

数组p临时申请的那块空间,在退出这个函数的时候,就被释放掉了。
虽然我们的str可以获得p的地址,但是p指向的东西未知,p就相当于野指针。访问时,就会造成非法访问
属于:返回栈空间地址问题。

2.2.2 解决问题

#include
#include
char* getmemory(void)
{
	static char p[] = "hello word";
	return p;
}
void test(void)
{
	char* str = NULL;
	str = getmemory();
	printf("%s", str);
	free(str);
	str = NULL;
}
int main()
{
	test();
	return 0;
}

【动态内存错误详解和C的内存分区】_第4张图片
运行成功
我只是增加了static静态修饰符。

static char p[] = "hello word";

为什么这样就可以呢?首先我们来回顾static的作用

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,
所以生命周期变长。

所以,我们使用static修饰过后,当退出这个函数时,这个变量就会一直保存,我们再次调用时,仍是保存上一次的调用结果。此时,str可以获得p的地址。并且,p不再是野指针。

c/c++内存分布

【动态内存错误详解和C的内存分区】_第5张图片
内核空间,内存映射段。

1.2 内存分区简介

1.2.1 栈区(stack)

栈区编译器自动分配释放,由操作系统自动管理,无须手动管理。
栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会自动被销毁。


1.栈区按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。
2.栈区是先进后出原则,即先进去的被堵在屋里的最里面,后进去的在门口,释放的时候门口的先出去。
栈区存放内容


临时创建的局部变量和const定义的局部变量存放在栈区。
函数调用和返回时,其入口参数和返回值存放在栈区。

1.2.2 堆区(heap)

堆区由程序员分配内存和释放。

堆区按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。

堆区动态申请与释放内存用malloc(),free()等函数实现动态分布内存。

1.2.3 全局(静态)区

通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。

全局区有 .bss段 和 .data段组成,可读可写

1 bss段
未初始化的全局变量和未初始化的静态变量存放在.bss段。
初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
.bss段不占用可执行文件空间,其内容由操作系统初始化。
2data段
已初始化的全局变量存放在.data段。
已初始化的静态变量存放在.data段。
.data段占用可执行文件空间,其内容有程序初始化。

1.2.4 常量区

字符串、数字等常量存放在常量区。 const修饰的全局变量存放在常量区。 程序运行期间,常量区的内容不可以被修改。

1.2.5 代码区

程序执行代码存放在代码区,其值不能修改(若修改则会出现错误)。 字符串常量和define定义的常量也有可能存放在代码区。

你可能感兴趣的:(c语言,开发语言)