c语言内存管理
2017-08-23
作用域
一个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函数应该注意一下几点:
- malloc的参数就是需要分配的内存的字节数。
- malloc所分配的是一块连续的内存。
- 分配成功,则返回指向分配内存起始地址的指针。分配失败,返回NULL指针。
- 对每个malloc返回的指针都进行检查,是好的习惯。
内存的初始化一般要在使用之前手动进行,也可以在分配时由calloc函数一同完成:将分配内存初始化为0。使用就是使用分配所返回的指向内存的指针。
释放内存是为了防止内存泄露,一般使用free函数(原型:void free(void *pointer))完成。它的参数必须是先前从malloc、calloc或realloc返回的值。向free传递一个NULL参数不会产生任何效果。向free传递其他参数会出错。
常见的动态内存错误
常见的动态内存错误有以下几种:
- 忘记对NULL指针进行解引用操作,即忘记对分配返回的值做判断。
- 对分配的内存进行操作时越过边界。
- 释放并非动态分配的内存,传递给free的必须是一个从malloc、calloc或realloc返回的指针。
- 试图释放一块动态分配的内存的一部分。
- 一块动态内存被释放后被继续使用。
总结
使用动态内存的编程总结:
- 动态内存分配有助于消除程序内部存在的限制。
- 使用sizeof计算数据类型的长度,调高程序的可移植性。
calloc和realloc函数:
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
calloc与malloc的区别:
- 前者返回指向内存的指针之前把它初始化为0.
- calloc的参数包括所需元素的数量和每个元素的字节数。根据这些值,能够计算出总共需要分配的内存。
- realloc函数用于修改一个原先已经分配的内存卡的大小。使用该函数,可以使一块内存扩大或缩小。如果原先的内存卡无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上。
示例
利用动态内存分配,写一个字符串追加方法。
#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;
}