c语言_Day37_08_09

1、动态内存分配

内存空间分布:

  • 栈:局部变量、函数形参
  • 堆:动态内存分配
  • 静态区:静态变量、全局变量

内存使用方式:

  • 创建变量
  • 创建数组

1、动态内存函数

1. malloc和free

malloc函数:用于开辟内存空间

参数:

size_t size 开辟内存的字节数

返回值:

void* 开辟内存空间的起始地址(成功)

​ 空指针(失败)

int main()
{
	int num = 10;
	// 向内存申请一块存放10个整形的空间
	int* p = (int*)malloc(num * sizeof(int));
	// 开辟失败,打印错误信息
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	// 开辟成功,正常使用
	else
	{
		for (int i = 0; i < num; i++)
		{
			*(p + i) = i;
			printf("%d\n", *(p + i));
		}
	}

	return 0;
}

free函数:释放动态内存空间

参数:

_Block 待释放的空间的指针

注:free函数仅用于释放归还空间,但是指针仍存在,故需要手动将指针赋值为空指针

int main()
{
	int num = 0;
	scanf("%d", &num);
	int* p = (int*)malloc(num * sizeof(int));
	system("cls");
	if (p)
	{
		for (int i = 0; i < num; i++)
		{
			*(p + i) = i;
			printf("%d\n", *(p + i));
		}
	}
	else
	{
		printf("%s\n", strerror(errno));
	}
	free(p);
	p = NULL;

	return 0;
}
2. calloc

calloc函数:内存中创建一个数组并初始化为0

参数:

size_t num 数组元素个数

size_t size 每个元素的字节大小

返回值:

数组的首元素地址

int main()
{
	int num = 20;
	int* arr = calloc(num, sizeof(int));
	if (arr)
	{
		for (int i = 0; i < num; i++)
		{
			printf("%d ", arr[i]);
		}
	}
	else
	{
		printf("%s", strerror(errno));
	}
	printf("\n");
	// 释放空间
	free(arr);
	arr = NULL;

	return 0;
}
3. realloc

realloc:调整动态内存的大小

参数:

void* block 待修改的动态内存的指针

size_t size 新空间的大小

返回值:

void* 返回指向重新 void分配的 (的指针,并且可能已移动) 内存块

realloc的风险:

realloc返回值的重新分配存在两种情况:

  • 若新内存空间足够,则返回指向原内存空间的指针
  • 若新内存空间不够(造成非法访问),则重新开辟新的空间并返回指向新空间的指针
  • 若新内存超过全部内存大小,则返回空指针
int main()
{
	int num = 40;
	char* p = (char*)malloc(num * sizeof(char));
	if (p)
	{
		for (int i = 0; i < num; i++)
		{
			*(p + i) = i;
			printf("%d\n", *(p + i));
		}
	}
	else
	{
		printf("%s\n", strerror(errno));
	}
	printf("=======================================\n");
	// 修改内存空间大小
	int new_num = 100;
	char* p2 = (char*)realloc(p, new_num);
	if (p2)
	{
		p = p2;
		for (int i = 0; i < new_num; i++)
		{
			*(p + i) = i;
			printf("%d\n", *(p + i));
		}
	}
	else
	{
		printf("%s\n", strerror(errno));
	}
    free(p);
    p = NULL;

	return 0;
}

注:若返回新的地址,则realloc函数内部自动释放旧地址

​ 若realloc的第一个参数传入空指针,则其功能等同于malloc

2、常见的动态内存错误

  1. 空指针操作
  2. 动态内存越界访问
  3. 使用free函数释放非动态内存的空间
  4. 使用free函数释放动态内存的一部分
int* p = (int*)malloc(10 * sizeof(int));
for (int i = 0; i < 10; i++)
{
    *p++ = i;
}
free(p);
p = NULL;

上述代码中,p的指向已发生变化(不再指向动态内存空间的起始地址),故使用free函数释放内存后程序会崩溃

  1. 对同一块动态内存多次释放

注:为防止上述情况发生,一定要保证free释放内存后再将指针赋值为空指针

  1. 忘记释放动态内存空间(内存泄漏)

3、笔试题

  1. 问题代码
void GetMemory(char* p)
{
    p = (char*)malloc(40);
}

void Test()
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str, "Hello, World!");
    printf("%s\n", str);
}

int main()
{
    Test();
}

问题:

  • 值传递问题:动态内存的地址仅由p保存,函数结束后p销毁,动态内存地址丢失
  • 动态内存未释放,导致内存泄漏

修改:

void GetMemory(char** p)
{
	*p = (char*)malloc(20 * sizeof(char));
}

void Test()
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "Hello, World!");
	printf(str);
	free(str);
	str = NULL;	
}

int main()
{
	Test();

	return 0;
}
  1. 问题代码
char* GetMemory()
{
    char arr[] = "Hello";
    return arr;
}

void Test()
{
    char* str = NULL;
    str = GetMemory();
    printf("%s\n", str);
}

int main()
{
    Test();
    
    return 0;
}

str虽然指向返回值arr,但arr在函数调用结束后销毁,打印str会造成非法访问内存

注:返回栈空间的地址存在风险

修改:

char* GetMemory()
{
    char* p = "Hello";
    return p;
}

void Test()
{
    char* str = NULL;
    str = GetMemory();
    printf("%s\n", str);
}

int main()
{
    Test();

    return 0;
}

返回栈空间时,函数调用结束栈空间释放;返回堆内存静态区内存可避免上述问题

  1. 问题代码
void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}

void Test()
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "Hello");
	printf(str);
}

int main()
{
	Test();

	return 0;
}

未释放内存,内存泄漏问题

  1. 问题代码
void Test()
{
	char* str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str);
    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}

int main()
{
	Test();

	return 0;
}

释放动态内存后并未将指针指向空指针,导致非法访问内存

4、内存空间解析

  • 栈:存放局部变量
int a;
int numArr[5];
char* p;
// ...
  • 数据段(静态区):存放static修饰的静态变量以及全局变量
int global_var = 20;
void Test()
{
    static int num = 10;
}
// ...
  • 堆:存放动态内存
malloc(10 * sizeof(int));
calloc(10, sizeof(int));
realloc(NULL, 10 * sizeof(int));
// ...
  • 代码段:存放如常量字符串等只读数据
"Hello, World!";
// ...

各内存空间的回收机制:

  • 栈:函数执行结束后自动释放
  • 堆:一般由程序员手动释放,若程序员不释放则在程序结束后由操作系统释放
  • 数据段:程序结束后由操作系统释放