首页 关于 微信公众号
欢迎关注我的微信公众号

c语言内存管理

作用域

一个C语言变量的作用域可以是代码块 作用域,函数作用域或者文件作用域。代码块是{}之间的一段代码。

auto 自动变量

一般情况下代码块内部定义的变量都是自动变量。当然也可以显示的使用auto关键字

register寄存器变量

通常变量在内存当中,如果能把变量放到CPU的寄存器里面,代码执行效率会更高。

代码块作用域的静态变量

静态变量是指内存位置在程序执行期间一直不改变的变量,一个代码块内部的静态变量只能被这个代码块内部访问。

代码块作用域外的静态变量

代码块之外的静态变量在程序执行期间一直存在,但只能被定义这个变量的文件访问。

全局变量

全局变量的存储方式和静态变量相同,但可以被多个文件访问。

内存四区

程序加载到内存之后,在内存中存放的位置根据代码类型而不同。把内存分为四个区域:

代码区

代码区code,程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段,这块内存是不可以在运行期间修改的。

栈区

栈stack是一种先进后出的内存结构,所有的自动变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域时,自动从栈中弹出。

堆区

堆heap和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。

堆是一个大容器,它的容量要远远大于栈,但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。

示例

示例01

#include <stdio.h>
#include <Windows.h>

#define MAXSIZE 1024

#pragma warning(disable:4996)

char *test01()
{
    char *str[10] = {"hello"};  // str 是在栈中,出作用域之后变量释放
    return str;
}

char *test02()
{
    return "hello";
}

在主函数中执行上述两个函数。

int main(){
    char *p1 = test01();
    printf("%s\n",p1);   // 输出随机值
    
    char *p2 = test02();
    printf("%s\n",p2);   // 正确输出 hello
    return 0;
}

示例02


#include <stdio.h>
#include <Windows.h>

#define MAXSIZE 1024

#pragma warning(disable:4996)

char *test03()
{
    char *s = malloc(sizeof(char) * 10);
    strcpy(s,"hello");
    return s;
}

void test04(char *p)
{
	p = malloc(sizeof(char) * 10);
	strcpy(p,"hello");
}


void test05(char *p)
{
	strcpy(p,"hello");
}

void test06(char **p)
{
	*p = malloc(sizeof(char) * 10);
	strcpy(*p,"hello");
}

在主函数中执行上述两个函数:

int main(){
    char *p3 = test03();
    printf("%s\n", p3);
    free(p3);        //  输出 hello
    
    
    
	char *s = NULL;
	test04(s);
	printf("%s\n",s);  // NULL
	

	char *s2 = malloc(sizeof(char) * 10);
	test05(s2);
	printf("%s\n",s2);   // 可以成功


	char *s = NULL;
	test06(&s);
	printf("%s\n",s);  // 输出hello
	
    
    return 0;
}

动态内存分配

原理

数组的元素存储于内存中连续的位置上。当一个数组被声明时,它所需要的内存在编译时就被分配。但是,我们可以使用动态内存分配在运行时为它分配内存。

一块内存的生命周期可以分为四个阶段:分配、初始化、使用、释放。

内存的分配一般使用C函数库里的malloc函数(原型:void *malloc(size_t size))。

关于malloc函数应该注意一下几点:

  1. malloc的参数就是需要分配的内存的字节数。
  2. malloc所分配的是一块连续的内存。
  3. 分配成功,则返回指向分配内存起始地址的指针。分配失败,返回NULL指针。
  4. 对每个malloc返回的指针都进行检查,是好的习惯。

内存的初始化一般要在使用之前手动进行,也可以在分配时由calloc函数一同完成:将分配内存初始化为0。使用就是使用分配所返回的指向内存的指针。

释放内存是为了防止内存泄露,一般使用free函数(原型:void free(void *pointer))完成。它的参数必须是先前从malloc、calloc或realloc返回的值。向free传递一个NULL参数不会产生任何效果。向free传递其他参数会出错。

常见的动态内存错误

常见的动态内存错误有以下几种:

  1. 忘记对NULL指针进行解引用操作,即忘记对分配返回的值做判断。
  2. 对分配的内存进行操作时越过边界。
  3. 释放并非动态分配的内存,传递给free的必须是一个从malloc、calloc或realloc返回的指针。
  4. 试图释放一块动态分配的内存的一部分。
  5. 一块动态内存被释放后被继续使用。

总结

使用动态内存的编程总结:

  1. 动态内存分配有助于消除程序内部存在的限制。
  2. 使用sizeof计算数据类型的长度,调高程序的可移植性。

calloc和realloc函数:

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

calloc与malloc的区别:

示例

利用动态内存分配,写一个字符串追加方法。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Windows.h>

#pragma warning(disable:4996)

// 字符串追加
char *array = NULL;  // 初始长度为10
void init_array(const char* s)
{
	if (array)
	{
		free(array);
	}

	int s_len = strlen(s);
	array = malloc(s_len + 1);
	strcpy(array, s);
}

void add_str(const char *s)
{
	if (s == NULL)
	{
		return;
	}

	int array_len = strlen(array);
	int s_len = strlen(s);

	// 首先开辟一个新的堆内存空间,内存大小为 array_len + s_len +1
	char *temp = malloc(array_len + s_len + 1);
	strcpy(temp, array);
	strcpy(temp + array_len, s);
	*(temp + array_len + s_len) = 0;  
	
	free(array);
	array = temp;

}

void add_str_new(const char *s)
{
	if (s == NULL)
		return;

	int len_array = strlen(array);
	int len_s = strlen(s);
	realloc(array, len_array + len_s +1);
	memset(array + len_array, 0, len_s + 1);
	strcpy(array + len_array, s);
}

void free_array()
{
	if (array)
		free(array);
	array = NULL;
}

int main()
{
	init_array("adfsdfddddddd");
	//add_str("1234dgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfg");
	add_str_new("1234dgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfgdfg");
	printf("array:%s\n", array);
	free_array();
	return 0;
}

Blog

Opinion

Project