在 C 语言中,当一个程序被加载到内存中运行,系统会为该程序分配一块独立的内存空间,并且这块内存空间又可以再被细分为很多区域,比如:栈区、堆区、静态区、全局区等
栈区:保存局部变量。存储在栈区的变量,在函数执行结束后,会被系统自动释放。
堆区:由 malloc、calloc、realloc……等函数分配内存。其生命周期由 free 函数控制,在没有被释放之前一直存在,直到程序运行结束。
定义在函数内部的局部变量,都保存在栈区。栈区的特点是:函数执行结束后,由系统 “自动回收”局部变量所对应的内存空间。所谓的“自动回收”其实是操作系统将这块栈内存又分配给其他函数中的局部变量使用。
当定义局部变量时,系统会在栈区为其分配一块内存空间,当函数执行结束后系统负责回收这块内存,又分配给其他局部变量使用。
以下代码执行后,会发现输出的a,b变量内存地址是一样的。可以说明局部变量对应的内存在函数执行结束后,会被系统回收分配给其他函数中的局部变量使用。因此,在C语言中,不能将局部变量的地址作为函数返回值返回,否则可能出现问题
#include
void showA()
{
int a;
printf("&a=%p\n",&a);
}
void showB()
{
int b;
printf("&b=%p\n",&b);
}
int main(void)
{
showA();
showB();
getchar();
return 0;
}
栈内存:
(1) 由系统自动分配、释放。如:函数形参、局部变量。
(2) 栈内存比较小,在 VS2012 中,栈内存默认最大为 1M,如果局部变量占用的栈内存过大,会发生栈溢出。
#include
int main(void)
{
int a[9900000];//会产生StackOverflow
getchar();
return 0;
}
使用 malloc 系列函数分配的内存都属于堆区,使用完后调用 free 函数进行释放,否则可能会造成内存泄漏(即这块内存无法被再次使用)。
malloc 函数
函数原型: void *malloc(int size);
头文件: #include
参数列表: size:分配多少个字节。
功能: 申请指定大小的堆内存。
返回值:如果分配成功则返回指向被分配内存的指针,否则返回空指针 NULL。
void *表示“不确定指向类型”的指针,使用前必须进行强制类型转化,将 void*转化为“确定指向类型”的指针。
free 函数
函数原型: void free(void* ptr);
头文件: #include
参数列表: ptr:指向要被释放的堆内存。
功能: 释放 ptr 指向的内存空间。
在C语言中,被 free 之后的堆内存,将会被操作系统回收再分配,不建议继续使用, 否则输出的结果将难以预料。
#include
#include
int main(void)
{
int *p = (int *)malloc(sizeof(int));//void *类型的返回值必须进行强制类型转化
*p = 200;
printf("%p, %d\n", p, *p);//输出指针p所指向的内存地址,输出此内存地址中的值
free(p);
getchar();
return 0;
}
堆内存
(1)由程序员自己申请、释放。如果没有释放,可能会发生内存泄露,直到程序结束后由系统释放。
(2)堆内存比较大,可以分配超过 1G 的内存空间。
函数返回数据的两种方式
#include
#include
int* getMemory()
{
int* p_int=(int*)malloc(sizeof(int));//被调函数分配使用内存
*p_int=100;
return p_int;
}
int main(void)
{
int* p=getMemory();
printf("%d\n",*p);
free(p); //主调函数释放内存
getchar();
return 0;
}
分配内存与释放内存是分开的,容易导致程序员忘记在主调函数中释放内存,从而导致内存泄漏,
2.在主调函数中分配堆内存,在被调函数中使用堆内存,最后又在主调函数中释放堆内存。
#include
#include
void fun(int *p_int)
{
*p_int=100;
}
int main(void)
{
int* p=(int*)malloc(sizeof(int)); //主调函数分配堆内存
fun(p);
printf("%d",*p);
free(p); //主调函数释放堆内存
getchar();
return 0;
}
此方法较为推荐
使用 malloc 函数分配的堆内存,系统不会初始化内存,分配的内存中还会残留旧数据。因此,引用未初始化的堆内存,输出的数据也将是未知的。
#include
#include
int main(void)
{
int *p_int=(int*)malloc(sizeof(int));
printf("%d",*p_int);//输出随机未知数据
getchar();
return 0;
}
为了避免引用堆内存中的未知数据,一般使用 malloc 在堆区分配内存后,需要将这块堆内存初始化为0
函数原型:void* memset(void *dest, int c, size_t size);
头文件: #include
参数列表: dest:被初始化的目标内存区域。 c:要设置的字符。 size:初始化 size 个字节。
功能: 将 dest 指向的内存空间前 size 个字节初始化为 c。 返回值: dest的内存地址。
#include
#include
int main(void)
{
char arr[10] = {0};
//会把arr的前5个字节的元素设置为@
memset(arr, '@',5);
getchar();
return 0;
}
函数原型:void* memcpy(void* destination, const void* source, size_t num);
memcpy函数会从source指针的位置向后复制num个字节的数据到destination指针指向的内存的位置中
但是source和destination的num个字节的数据在内存中不能有重叠,否则复制的结果是未知的
memcpy可以复制任意类型的数据
#include
#include
struct S
{
int age;
char name[5];
};
int main(void)
{
struct S arr1[] = {{18,"abc"}, {22, "456"}};
struct S arr2[3] = {0};
//从内存中可以看到,arr1的数据已经拷贝到arr2中
memcpy(arr2, arr1, sizeof(arr1));
getchar();
return 0;
}
简单实现my_memcpy
void* my_memcpy(void* dest, void* source, size_t num)
{
assert(dest && source);
void* res = dest;
while(num--){
*(char*)dest = *(char*)source;
++(char*)dest;
++(char*)source;
}
return res;
}
函数原型:void* memmove(void* destination, const void* source, size_t num);
memmove函数会从source指针的位置向后复制num个字节的数据到destination指针指向的内存的位置中
但是source和destination的num个字节的数据在内存中可以有重叠
函数原型:int memcmp(void* ptr1, void* prt2, size_t num);
比较从ptr1指针以及ptr2指针开始的num个字节的数据的大小
#include
#include
int main(void)
{
int res = 0;
char arr1[] = {1,2,3,4,5};
char arr2[] = {1,2,4,5,5};
//比较
res = memcmp(arr2, arr1,3);
printf("%d", res);
getchar();
return 0;
}