堆内存管理&字符串
2019.5.25
堆内存管理
什么是堆内存
程序在内存是分段:
- 代码段+只读段:二进制指令,常量
- 全局段:初始化过和的全局变量,静态变量
- 静态数据段:没有初始化过和的迁居变量,静态变量
- 栈:局部变量,块变量(采用栈的方式进行管理)
- 堆:数据无序的存储到这一块内存中,受物理内存的限制
为什么使用堆内存
1. 栈内存的大小有限
2. 栈内存中的数据释放不受程序员控制(函数结束后,属于它的内存就会被系统自动释放,它不适合长期存放数据)
如何使用堆内存
- 堆内存无法与标识符建立对应关系(必须使用指针来指向堆内存)
- C语言中没有管理堆内存的语句,但标准库提供一套函数来管理堆内存
- 堆内存的管理和释放由程序员手动操作(显示调用函数来管理)
void *malloc(size_t size);
- 功能:从栈内存申请size个字节
- size:要申请的字节数
- 返回值:所申请到的内存首字节地址需要使用指针来接收
注意:使用malloc所申请的堆内存的内容默认情况下不确定(但首次使用时是0),如果size的值为0,返回值是NULL
练习1:计算100-1000所有的素数,存储在堆内存中
void bzero(void *s,size_t n);
- 功能:所一块内存的所有字节,设置为0(清理内存)
- s:要清理的内存首地址
- n:清理的字节数
void*
是一种无类型的指针
- 不能直接使用,不能通过这种指针变量解引用(必须转换为其他类型)
- 可以与任意类型的指针进行自动转换
void* = 任意类型
任意类型 = void*
- void* 也叫万能指针,解决函数之间传递指针参数时类型不确定问题
void *menset(void *s, int c, size_t n);
- 功能:把内存的每一个字节都设置为c
- s:内存首地址
- c:字节中要设置的数据,-128~127
- n:要设置的字节数
- 返回值:初始化的内存首地址,也就是s的值(链式调用)
void free(void *ptr);
- 功能:释放内存,只是把内存的使用权收回,内容还在。
- ptr:malloc的返回值,之前所申请的内存的首地址。
注意:
内存释放后,还能继续访问(不产生段,可能会造成脏数据),但是在非法访问。
内存在释放后,指针要立即置NULL,否则指针就会变成野指针。
一块内存不能释放两次,否则会出现堆崩溃。
void *calloc(size_t nmemb, size_t size);
- 功能:从堆内存申请nmemb*size个字节(内存已初始化为0)
- size:每次申请的字节数
- nmemb:申请的次数
- 返回值:所申请的内存的首地址(size或nmemb只要有一个0,返回值为NULL)
注意:calloc的速度会比malloc慢,因此绝大多数情况下只使用malloc
void *realloc(void *ptr, size_t size);
- 功能:调整已申请到的内存的大小。
- ptr:内存的首地址,malloc或calloc的返回值,如果ptr为NULL而size大于0,相当于申请内存。
- size:把内存调整为size个字节,可以调大,也可以调小,如果ptr的值合法,而size的值为0,则相当于释放内存。
- 返回值:调整后的内存的首地址,一定要** 重新接收 ** 。
练习2:修改练习1,不浪费内存。
使用内存时要注意的问题
- 当程序结束后,所有属于它的所有资源都会被操作系统回收
内存泄漏:
- 由于失误而忘记或无法释放堆内存,导致堆内存无法循环利用,从而每次重新申请内存使可用内存越来越少。
-
保护指针不被修改
类型* const p = malloc(size);
-
malloc
和free
成对出现谁申请谁释放
定位内存泄漏
- 当程序运行后观察内存的使用情况,内存暴涨检查循环中的申请内存
cat /proc/meminfo
linux下查看内存使用情况 - 检查每个malloc的free(在free前查是否已经变成空指针)
- 检查条件,业务逻辑free会被对用到
内存碎片:
- 已经释放但不能被再次分配使用的内存
- 频繁的申请,释放内存,导致申请和释放的不协调,一部分内存无法再次被使用。
- 内存碎片无法杜绝,只能尽量减少
如何减少内存碎片
- 尽量减少使用堆内存,栈内存可以解决的尽量使用栈内存
- 尽量申请大块的内存
- 不要频繁的申请和释放
字符:
- 字符就是符号,图案,在计算机中以整数形式存储,当需要显示的时候根据ASCII表中的对应关系,显示相应的符号和图案
CHAR | ASCII |
---|---|
'0' | 48 |
'a' | 97 |
'A' | 65 |
'\0' | 0 |
字符输入:
scanf("%c",&ch);
ch = getchar();
字符输出:
printf("%c",ch);
putchar(ch);
注意:先输入整数,浮点代数据,会影响后续字符数据的输入(把回车当成字符获取)
串:
- 是一种数据结构,由若干个相同类型的元素组成,有一个明确的结束标志。
字符串
- 由字符组成的串行结构,它的结束标志是’\0’
注意: 所有对字符串的操作都以’\0’作为结束标志
字符串的存在形式
字符数组:char arr[] = {'a','b','c','d'};
注意:要为'\0'
预留位置
字符串字面值:"双引号包括的若干个字符,末尾隐藏着\0"
以地址形式存在
注意:存储在只读段,不能被修改否则会产生段错误
- 一般常用字符串字符值来初始化字符数组,编译器自动把字符串字面值拷贝到字符数组中,包括
'\0'
之后这个字符串就有了两份,一份在栈中,一份在只读段。
字符串的输出
printf %s 指针
puts(指针) 会自动在末尾增加一个'\n'
字符串的输入 '\0'
会自动添加
scanf %s 指针//不接收空格
gets(指针);//可以接收空格
fgets(指针,size,stdin);//只能接收到size-1个字符,为'\0'预留
当输入的字符不足size-1个是,会连\n一起接收
stdin->_IO_read_end = stdin->_IO_read_ptr//清缓冲区
练习1:实现一个计算字符串长度的函数,不包括’\0’
练习2:实现一个字符串输入函数,可以指定字符的数量,但不接收到’\n’
字符串的操作
strlen
计算字符串长度
strcpy(str,"haha")
拷贝字符串,相当于赋值(只有初始化才能用=赋值)
strcat(str,"haha")
连接两个字符串
strcmp()
按字典序比较字符串大小
condition | return |
---|---|
str1 == str2 | 0 |
str1 > str2 | 1 |
str1 < str2 | -1 |
atoi/atol/atoll("123a")
字符串转整数
strstr
在str1中查找str2,返回str2首次出现的位置,没有NULL
作业1:实现itoa函数
作业2:实现一个函数,用于判断字符串是不是回文串
作业3:实现一个字符串逆序函数
作业4:把字符串中的单词首字母大写,空格替换为“% %”
作业5:把一段文字的单词逆序
作业6:计算字符串2在字符串1中出现的次数
作业7:实现两个大数相加