8.C(内存管理)
在程序没有执行前,有几个分区已经确定,不过分区虽然确定,但是没有加载内存,程序只有运行时才加载内存
text(代码区):只读,函数
data:初始化的数据,全局变量,static变量,文字常量区
bss:没有初始化的数据,全局变量,static变量
当程序运行,加载内存,首先根据前面确定的内存分区先加载,然后额外加载两个区,stack heap
stack(栈区):普通局部变量,自动管理内存,先进后出的特点
heap(堆区):手动申请空间,手动释放,整个程序结束,系统也会自动回收
栈溢出
Linux查看栈等内存信息.png从上边截图可以看出,栈的大小只有8兆左右,所以是很容易发生溢出的问题的,我们来模拟一下栈溢出的情况
#include <stdio.h>
//假设我们定义了一个int数组,数组大小如下,vs会
直接在编译期间就告诉我们,数组太大,那我们去掉
一个0试试,编译可以通过了(在Windows上vs验
证,数组最大可以定义10亿个元素,超过则编译无法
通过),但是运行后会提示错误
int main() {
int a[10000000000] = { 0 };
system("pause");
}
运行后会提示错误
严重性 代码 说明 项目 文件 行 禁止显示状态
错误 C2148 数组的总大小不得超过 0x7fffffff 字
节 ConsoleApplication3 c:\users\renzhenming
\documents\visual studio 2015\projects
\consoleapplication3\consoleapplication3\1.c 5
因为局部变量a是存储在栈内存的,由于栈内存大小的限制,我猜测vs对数组的大小也做了限制,但是在Linux上通过gcc编译会直接栈溢出
堆溢出
如下,在堆内存开辟一个大小为0的空间,让p指向它,我们发现,p的返回值不是NULL,说明内存申请成功了,但是其实它是0,等于并没有申请,有的编译器无法识别这个错误(gcc直接在Linux编译,不会报错,甚至还会打印出拷贝的字符串,vs中则会报错)。这时候将一段字符串拷贝过去,就会发生堆内存溢出的问题
#include <stdio.h>
int main() {
char *p = NULL;
p = (char*)malloc(0);
if (p == NULL)
{
printf("分配失败\n");
return 0;
}
strcpy(p,"hello world");
printf("%s\n",p);
if (p != NULL) {
free(p);
p = NULL;
}
system("pause");
}
内存管理
1.memset()
将s的内存区域的前n个字节以参数c填入
s是需要操作的内存的首地址
c是填充的字符,虽然为int类型,但必须是unsigned char ,范围是0~255
n是指定需要设置的大小
返回值为s的首地址
#include <string.h>
void *memset(void *s,int c,size_t n);
虽然第二个参数为int类型,但其实是以字符填充的,当第二个参数为0表示将内存清空初始化或者说归零,但是如果设置为其他的数字,比如说1,那内存变成什么样子,你可以打印看看,如果是像下边一样,设置为97那就是将数组中每一个元素都设置为小写a字符,因为a的ASSIC码就是97
#include <stdio.h>
#include <string.h>
int main() {
int a[10];
printf("%d\n", sizeof(a));
memset(a,97,sizeof(a));
//memset(a, 97, sizeof(a));
int i = 0;
for (; i < 10; i++) {
printf("%c\n",a[i]);
}
system("pause");
}
多用于将数组归0
#include <stdio.h>
#include <string.h>
int main() {
int b[19] = {1,2,3,4};
memset(b,0,sizeof(b));
for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++) {
printf("%d\n",b[i]);
}
system("pause");
}
1.memcpy()
拷贝src所指内存内容的前n个字节到dest所指的内存地址
dest:目的内存首地址
src:源内存首地址,注意,dest和src所指内存不能重叠
#include<string.h>
void *memcpy(void *dest,const void *src,size_t n);
//memcpy是不安全的,需要加上这个宏定义
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main() {
char p[] = {"hello\0world"};
char buf[100];
printf("sizeof(p)=%lu\n",sizeof(p));
//使用strncpy拷贝一个字符串,遇到\0会结束拷贝,所以打印的是hello,
strncpy(buf, p, sizeof(p));
printf("buf1=%s\n",buf);
printf("buf2=%s\n", buf+strlen("hello")+1);
//将内存重置为0
memset(buf, 0, sizeof(buf));
//使用memcpy拷贝内存,不会因为存在\0就结束,而是拷贝整个
//指定的内存过去
memcpy(buf,p,sizeof(p));
//这里解释一下,由于字符串p中含有结束符,所以拷贝到数组buf中后
//直接打印数组名也就是指针,只会打印出结束符之前的内容,所以才会
//这样打印两次
printf("buf3=%s\n", buf);
printf("buf4=%s\n", buf + strlen("hello") + 1);
system("pause");
}
打印
sizeof(p)=12
buf1=hello
buf2=
buf3=hello
buf4=world
请按任意键继续. . .
3.memmove()
将一个数组的前五个元素移动到本数组从第三个元素开始的位置
#include <stdio.h>
#include <string.h>
int main() {
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int b[10];
//使用memcpy最好别出现内存重叠,如果出现,最好
//使用memmove
memmove(&a[2],a,5*sizeof(int));
int i = 0;
for (; i < sizeof(a)/sizeof(a[0]); i++) {
printf("%d\n",*(a+i));
}
system("pause");
}
打印结果
1
2
1
2
3
4
5
8
9
10
请按任意键继续. . .
4.memcmp()
#include <stdio.h>
#include <string.h>
int main() {
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int b[10] = { 10,2,3,4,5,6,7,8,9,0 };
//比较前十个元素,比较的是每个元素的Assic码,一个一个比较
//只要有一个比如说a第一个元素小于b第一个元素,那么结果就是a<b
//这样比较没有多大意义,通常是用来比较两块内存是否相同
int flag = memcmp(a,b,10*sizeof(int));
if (flag < 0) {
printf("a <b\n");
}
else if (flag > 0) {
printf("a >b\n");
}
else {
printf("a =b\n");
}
system("pause");
}
指针指向栈区空间
#include <stdio.h>
int main() {
int *p;
//定义一个存储在栈区的局部变量a
int a = 10;
p = &a;
printf("*p=%d\n",*p);
system("pause");
}
指针指向堆区空间
malloc
参数是指定堆区分配多大的空间
返回值,内存分配成功则返回地址首元素
失败则返回NULL
1.动态分配的空间,如果程序没有结束,不会自动释放
2.使用完成后,通过free(p)释放
3.free(p)不是释放p变量,而是释放p指向的内存
4.同一块堆区内存只能释放一次
5.释放后的内存并不会消失,只是用户不能再使用
#include <stdio.h>
int main() {
int *p;
//在堆区开辟一块内存,由p指向它
p = (int*)malloc(sizeof(int)*10);
if (p == NULL)
{
printf("分配失败\n");
}
*p = 20;
printf("*p=%d\n",*p);
//避免重复free发生错误,当分配空间成功时
//p就不是NULL了,此时使用完成后将其free掉,并且设置
//p为NULL,下次就不会走过来free了
if (p != NULL)
{
free(p);
p = NULL;
}
system("pause");
}
值传递1
在栈内存定义一个指针p后,并没有指定它的指向,所以他是一个野指针,通过fun方法将p传递进入,此时,会在栈中再次定义一个指针temp,并在堆区开辟空间,由temp指向它,然后方法执行完毕,temp指针释放,但是堆区内存没有释放,内存泄漏,由于p指针仍然没有指向,所以通过*p操作其内存报错
#include <stdio.h>
void fun(int *temp) {
temp = (int*)malloc(sizeof(int));
*temp = 100;
}
int main() {
int *p;
fun(p);
printf("*p = %d\n",*p);
system("pause");
}
运行:
严重性 代码 说明 项目 文件 行 禁止显示状态
错误 C4700 使用了未初始化的局部变量“p”
ConsoleApplication3 c:\users\renzhenming
\documents\visual studio 2015\projects
\consoleapplication3\consoleapplication3\1.c 10
值传递2
在传递p之前先开辟空间供其指向,然后传递到fun方法中,fun方法中的temp变量和p变量指向相同,都指向在堆区开辟的那块内存,通过temp操作内存后,内存赋值100,方法弹栈,temp消失,但是p仍然指向最初的那块内存,仍可操作它
#include <stdio.h>
void fun(int *temp) {
*temp = 100;
}
int main() {
int *p;
p = (int *)malloc(sizeof(int));
fun(p);
printf("*p = %d\n",*p);
system("pause");
}
返回堆区的地址
#include <stdio.h>
int* fun() {
int *temp = NULL;
temp = (int*)malloc(sizeof(int));
*temp = 100;
return temp;
}
int main() {
int *p = NULL;
p = fun();
printf("*p = %d\n",*p);
if (p == NULL) {
free(p);
p = NULL;
}
system("pause");
}