8.C(内存管理)

2018-05-22  本文已影响17人  任振铭

在程序没有执行前,有几个分区已经确定,不过分区虽然确定,但是没有加载内存,程序只有运行时才加载内存

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");
}
上一篇 下一篇

猜你喜欢

热点阅读