文章目录
Linux-C P9 内存管理
内存管理基础
基础概念
什么是内存管理?
内存的使用时程序设计中需要考虑的重要因素之一,这不仅由于系统内存是有限的,而且内存分配也会字节映像到程序的效率。所以做好内存管理是非常重要的
上来一顿操作
这样,先来看一个程序的内存分布情况来分析
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
printf("Hello\n");
return 0;
}
这里运行了结果,并查看了元数据类型、文件的编码格式和文件数据的各个段及大小
ls命名这里不多做介绍(参见Linux-C P1),这里介绍一下file命令和size命令
在此之前先来看看这部分
文件类型与权限 | 链接占用的节点 | 文件所有者 | 文件所有者用户组 | 文件大小 | 文件的创建时间或最后修改时间 | 文件名称 |
---|---|---|---|---|---|---|
-rwxrwxr-x | 1 | linux | linux | 7291 | Jan 5 20:56 | a.out |
file命令
内容 | |
---|---|
命令名称 | file |
命令格式 | file [-bcLvz][-f <名称文件>][-m <魔法数字文件>…][文件或目录…] |
命令功能 | -b 列出辨识结果时,不显示文件名称 |
-c 详细显示指令执行过程,便于排错或分析程序执行的情形 | |
-f<名称文件> 指定名称文件,其内容有一个或多个文件名称时,让file依序辨识这些文件,格式为每列一个文件名称 | |
-L 直接显示符号连接所指向的文件的类别 | |
-m<魔法数字文件> 指定魔法数字文件 | |
-v 显示版本信息 | |
-z 尝试去解读压缩文件的内容 | |
[文件或目录…] 要确定类型的文件列表,多个文件之间使用空格分开,可以使用shell通配符匹配多个文件 |
当然这里只做简单的使用来辨识文件的类型
size命令
内容 | |
---|---|
命令名称 | size |
命令格式 | size [-A |
命令功能 | 控制输出格式。-A 或 --format=sysv 表示使用 System V size 风格 |
-B 或 --format=berkeley 表示使用 Berkeley size 风格 |
代码区 | 数据区 | 未初始化区 | 十进制总和 | 十六进制总和 | 文件名 |
---|---|---|---|---|---|
text | data | bss | dec | hex | filename |
1102 | 280 | 4 | 1386 | 56a | a.out |
这里就可以看出,一个程序由代码区、数据区和未初始化区,它还包括堆区和栈区,下面就来详细了解这些内存区间
内存区间
为什么查看内存分布情况的时候只有三个分区,而这里说内存一共由5个分区组成呢?
先来看看程序存储时和运行时的分区情况
因为堆区和栈区在运行时才会被分配和使用
代码区(text segment)
代码区主要存放程序中的代码,代码区指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次,如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈来实现
代码段(code segment/text segment )通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序
全局变量与静态变量区(data segment)
全局变量与静态变量区也称为静态存储区域,内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在
未初始化数据区(bss)
未初始化数据区(bss)用来存放程序中未初始化的全局变量的一块内存区域
BSS是英文Block Started by Symbol 的简称,BSS 段属于静态内存分配,即程序一开始就将其清零了,一般在初始化时BSS段部分将会清零
栈区(stack)
在栈上创建,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放
由编译器自动分配释放,存放函数的参数值、局部变量的值等
堆区(heap)
用于动态内存分配,有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为它们预先分配空间,只能在程序运行时才分配
分配方式
为什么要分配内存
(1)一个进程在运行过程中,代码是根据流程依次执行的,只需要访问一次,当然跳转和递归有可能使代码执行多次,而数据一般都需要访问多次,因此单独开辟空间以方便访问和节约空间。
(2)临时数据及需要再次使用的代码在运行时放入栈区中,生命周期短。
(3)全局数据和静态数据有可能在整个程序执行过程中都需要访问,因此单独存储管理。
(4)堆区由用户自由分配,以便管理。
静态分配与动态分配
静态分配:编译器在处理程序源代码时分配
动态分配:程序在执行时调用malloc库函数申请分配
栈和堆的区别
申请方式
栈:是由系统自动分配的
堆:需要程序员自己申请
申请大小
栈:是向低地址扩展的数据结构,是一块连续的内存区域
堆:是向高地址扩展的数据结构,是不连续的内存区域
申请速度
栈:由系统自动分配,速度较快
堆:由malloc等语句分配内存,一般速度比较慢
存储内容
栈:在调用函数时,第一个进栈的是函数调用语句的下一条可执行语句的地址,然后是函数的各个参数,参数是由右往左入栈的,然后是函数中的局部变量;当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始的存储地址,也就是调用该函数处的下一条指令,程序由该点继续运行
堆:一般在堆的头部用一个字节存放堆的大小
动态内存管理
动态内存基础
当程序运行到需要一个动态分配的变量时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量。当不再使用该变量时, 也就是它的生命结束时,要显式释放它所占用的存储空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源
申请动态内存空间(malloc)
内容 | |
---|---|
函数名称 | malloc |
所需头文件 | #include <stdlib.h> |
函数原型 | void *malloc(size_t size) |
函数功能 | 分配所需的内存空间,并返回一个指向它的指针 |
函数传入值 | size – 内存块的大小,以字节为单位 |
函数返回值 | 指向内存空间的指针 |
一般形式
#include <stdlib.h>
void *malloc(size_t size)
注意事项
1.只申请内存的大小,单位为字节
2.申请的是一块连续的内存
3.返回值类型是void*,不是某种具体类型的指针
4.显示初始化
举个栗子
这里来进行一个简单的分配动态空间,然后给分配好动态空间的变量赋值
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char *s1;
char *s2 = "hello!";
s1 = (char *)malloc(sizeof(s2));
strcpy(s1,s2);
printf("s = %s\n",s1);
return 0;
}
输出结果可以看出,输出了正确的结果,但是在后面报了一段错误,为什么呢?
这就是接下要讲的free要发挥作用了,因为动态内存编译器是不负责释放的,所以在使用完之后需要进行手动释放
释放动态内存空间(free)
内容 | |
---|---|
函数名称 | free |
所需头文件 | #include <stdlib.h> |
函数原型 | void free(void *ptr) |
函数功能 | 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间 |
函数传入值 | ptr 指针指向一个要释放内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的 |
函数返回值 | 无返回值 |
一般形式
#include <stdlib.h>
void free(void *ptr)
注意事项
1.必须提供内存的起始地址,不能提供部分地址,释放内存中的一部分是不允许的
2.malloc和free配对使用,编译器不负责动态内存的释放,需要程序员手动释放
3.不允许重复释放
4.free只能释放堆空间
举个栗子
对上面的栗子进行修改
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char *s1;
char *s2 = "hello!";
s1 = (char *)malloc(sizeof(s2));
strcpy(s1,s2);
printf("s = %s\n",s1);
free(s1);
return 0;
}
这样就是一个完整的动态内存分配与释放的程序
申请并初始化内存空间(calloc)
内容 | |
---|---|
函数名称 | calloc |
所需头文件 | #include <stdlb.h> |
函数原型 | void *calloc(size_t nitems, size_t size) |
函数功能 | 分配所需的内存空间,并返回一个指向它的指针 |
函数传入值 | nitems :要被分配的元素个数 |
size:元素的大小 | |
函数返回值 | 返回一个指针,指向已分配的内存 |
同样是是分配内存空间,与malloc不同的是,calloc可以选择分配元素的大小,并选择分配元素的多少
一般形式
#include <stdlb.h>
void *calloc(size_t nitems, size_t size)
举个栗子
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int i,n;
int *x;
printf("要输入元素的个数:");
scanf("%d",&n);
x = (int *)calloc(n,sizeof(int));
for(i = 0;i < n;i++){
printf("输入第%d个数字: ",i+1);
scanf("%d",&x[i]);
}
printf("--------------------\n");
printf("输入的数字为:");
for(i = 0;i < n;i++){
printf("%d ",x[i]);
}
printf("\n");
free(x);
return 0;
}
重新分配内存空间(realloc)
内容 | |
---|---|
函数名称 | realloc |
所需头文件 | #include <stdlib.h> |
函数原型 | void *realloc(void *ptr, size_t size) |
函数功能 | 尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小 |
函数传入值 | ptr – 指针指向一个要重新分配内存的内存块 |
size – 内存块的新的大小,以字节为单位 | |
函数返回值 | 返回一个指针 ,指向重新分配大小的内存 |
对于realloc,就是已经通过malloc、calloc或者realloc分配的内存空间,重新进行分配的函数
一般形式
#include <stdlib.h>
void *realloc(void *ptr, size_t size)
举个栗子
同样这里来修改一下malloc的栗子
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char *s1;
char *s2 = "hello!";
char *s3 = "Hello World!";
s1 = (char *)malloc(sizeof(s2));
strcpy(s1,s2);
printf("s = %s\n",s1);
s1 = (char *)realloc(s1,sizeof(s3));
strcpy(s1,s3);
printf("s = %s\n",s1);
free(s1);
return 0;
}
内存处理函数
使用一个常量字节填充内存空间(memset)
内容 | |
---|---|
函数名称 | memset |
所需头文件 | #include <string.h> |
函数原型 | void *memset(void *str, int c, size_t n) |
函数功能 | 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符 |
函数传入值 | str :指向要填充的内存块 |
c:要被设置的值,以int形式传递 | |
n:要被设置为该值的字节数 | |
函数返回值 | 该值返回一个指向存储区 str 的指针 |
一般形式
#include <string.h>
void *memset(void *str, int c, size_t n)
举个栗子
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char *s1;
char *s2 = "hello!";
s1 = (char *)malloc(sizeof(s2));
strcpy(s1,s2);
printf("s = %s\n",s1);
memset(s5,'*',5);
printf("s = %s\n",s1);
free(s1);
return 0;
}
拷贝内存空间(memcpy - memmove)
内容 | |
---|---|
函数名称 | memcpy |
所需头文件 | #include <string.h> |
函数原型 | void *memcpy(void *str1, const void *str2, size_t n) |
函数功能 | 从存储区 str2 复制 n 个字符到存储区 str1 |
函数传入值 | n:要被复制的字节数 |
str1:指向用于存储复制内容的目标数组 | |
str2:指向要复制的数据源 | |
函数返回值 | 该函数返回一个指向目标存储区 str1 的指针 |
一般形式
#include <string.h>
void *memcpy(void *str1, const void *str2, size_t n)
举个栗子
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char *s1;
char *s2 = "hello world!";
s1 = (char *)malloc(sizeof(s2));
memcpy(s1,s2+6,5);
s1[5]='\0';
printf("s = %s\n",s1);
free(s1);
return 0;
}
内容 | |
---|---|
函数名称 | memmove |
所需头文件 | #include <string.h> |
函数原型 | void *memmove(void *str1, const void *str2, size_t n) |
函数功能 | 从 str2 复制 n 个字符到 str1,如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改 |
函数传入值 | str1:指向用于存储复制内容的目标数组 |
str2:指向要复制的数据源 | |
n:要被复制的字节数 | |
函数返回值 | 该函数返回一个指向目标存储区 str1 的指针 |
一般形式
#include <string.h>
void *memmove(void *str1, const void *str2, size_t n)
举个栗子
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char *s1;
char *s2 = "hello world!";
s1 = (char *)malloc(sizeof(s2));
memmove(s1,s2+6,5);
s1[5]='\0';
printf("s = %s\n",s1);
free(s1);
return 0;
}
比较内存空间(memcmp)
内容 | |
---|---|
函数名称 | memcmp |
所需头文件 | #include <string.h> |
函数原型 | int memcmp(const void *str1, const void *str2, size_t n)) |
函数功能 | 把存储区 str1 和存储区 str2 的前 n 个字节进行比较 |
函数传入值 | str1:指向内存块的指针 |
str2:指向内存块的指针 | |
n:要被比较的字节数 | |
函数返回值 | 如果返回值 < 0,则表示 str1 小于 str2 |
如果返回值 > 0,则表示 str2 小于 str1 | |
如果返回值 = 0,则表示 str1 等于 str2 |
一般形式
#include <string.h>
int memcmp(const void *str1, const void *str2, size_t n))
举个栗子
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char *s1;
char *s2 = "hello!";
char *s3 = "Hello World!";
int r;
s1 = (char *)malloc(sizeof(s2));
strcpy(s1,s2);
r = memcmp(s1,s3,5);
printf("r = %d\n",r);
free(s1);
return 0;
}
在内存空间中搜索一个字符(memchr)
内容 | |
---|---|
函数名称 | memchr |
所需头文件 | #include <string.h> |
函数原型 | void *memchr(const void *str, int c, size_t n) |
函数功能 | 在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字符)的位置 |
函数传入值 | str:指向要执行搜索的内存块 |
c:以int形式传递的值,传递需要查找的字符的值 | |
n:要被分析的字节数 | |
函数返回值 | 该函数返回一个指向匹配字节的指针,如果在给定的内存区域未出现字符,则返回 NULL |
一般形式
#include <string.h>
void *memchr(const void *str, int c, size_t n)
举个栗子
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
char *s1;
char s2[] = "Hello World!";
s1 = (char *)memchr(s2,'W',strlen(s2));
printf("s = %s\n",s1);
return 0;
}