C语言之关于程序中内存的问题(内存是从哪里来的)

一 :程序执行需要内存支持

(1)对于程序来说,内存就是程序的立足之地(程序是被放在内存中运行的);程序运行时需要内存来存储一些临时变量。

二 :内存管理最终是由操作系统完成的(有操作系统的情况下)

(1)内存本身在物理上是一个硬件器件,由硬件系统提供

(2)内存是由操作系统统一管理。为了内存管理方便又合理,操作系统提供了多种机制来让我们应用程序使用内存。这些机制彼此不同,各自有各自的特点,我们程序根据自己的实际情况来选择某种方式获取内存(在操作系统处登记这块内存的临时使用权限。)、使用内存、释放内存(向操作系统归还这块内存的使用权限)。

三 :三种内存来源:栈(stack)、堆(heap)、数据区(.data)

(1)在一个C语言程序中,能够获取内存就是三种情况:栈(stack)、堆(heap)、数据区(.data)

四 :栈的详解

(1)运行时自动分配&自动回收:栈是自动管理的,程序员不需要手工干预(写代码来管理内存)。(譬如定义一个局部变量,编译器自动在栈找一个地址将这个地址和定义的局部变量绑定起来)方便简单。

(2)反复使用:栈内存在程序中其实就是那一块空间,程序反复使用这一块空间。

(3)脏内存:栈内存由于反复使用,每次使用后程序不会去清理,因此分配到时保留原来的值

(4)临时性:(函数不能返回栈变量的指针,因为这个空间是临时的)

#include <stdio.h>


//函数不能返回局部变量的地址,因为函数执行完之后这个局部变量已经不在了
//这个局部变量是分配在栈上的,虽然局部变量没了但是栈内存还存在还可以访问
//但是访问时这个地址已经和之前定义的局部变量无关了
int *func(void)
{


    int a = 4;            //a是一个临时变量,分配在栈上又叫栈变量,又叫临时变量

    printf("a = %p\n",&a);    //打印临时变量a的地址
    return &a;                //在linux系统gcc编译器会提醒警告函数返回变量的地址


}

int func1(void)
{

    int a = 33;
    int b = 44;
    int c = 55;

}


int main(void)
{


    int *p = NULL;
    
    p = func();                    
    
    printf("p = %p\n",p);          //  打印指针指向的地址(不同编译器可能结果不同,旧版可以    
                                   //打印较新版本显示结果是p = (nil))
    printf("*p = %d\n",*p);        //  按理来说临时变量在函数内用完就释放了,但是再次访问
                                   //指针指向的函数返回的地址内存存放的还是4,所以证明栈使用 
                                   //完后是脏的 (旧版可以打印出4,较新的会报错分段故障(堆芯        
                                   //倾倒))   

    func1();
    func1();        //由于栈的内存是操作系统分配的一块空间,当定义局部变量时,操作系统从分配给
                    //栈内存的第一个位置依次给(就像数组指针指向数组是的地址是数组的首元素首地        
                    //址a[0]是第一个,而a[9]是第10个),当函数结束局部变量释放时,下个函数继续定义临时变量时操作系统给它在栈中分配的地址依然是从a[0]开始分配
    printf("*p = %d\n",*p);        //所以当这里再次打印指针指向的那个地址的值时已经变成33了
                                   //所以这里确实证明栈内存空间是重复使用的
    

}

(5)栈会溢出:因为操作系统事先给定了栈的大小,如果函数无穷尽的分配栈内存总能够用完

    操作栈溢出的方法 :定义一个庞大的数组或定义递归函数定义临时变量

#include <stdio.h>


void func(void)
{

    int a[10000000] = {0};
    a[10000000-1] = '12';

}

void func1(void)
{
    int a = 4;
    func1();

}

int main(void)
{

    func();
    func1();    //都会造成栈溢出


    return 0;

}

五 :堆内存详解

(1)操作系统堆管理器管理:堆管理器是操作系统的一个模块,堆管理内存分配灵活,按需分配

(2)大块内存:堆内存管理着总量很大的操作系统内存块,各进程可以按需申请使用,使用完释放

(3)程序手动申请&释放:手工意思是需要写代码去申请malloc和释放free

(4)脏内存:堆内存也是反复使用的,而且使用者用完不会去清楚,因此也是脏的

(5)临时性:堆内存只在malloc和free之间属于我这个进程,而可以访问。而在这个之后都不能再访问,否则会有不可预料的后果

六 :堆内存使用范例

#include <stdio.h> 
#include <stdlib.h>   //malloc函数所在的库

int main(void)
{

    //需要一千个int类型元素的数组
    //第一步 : 申请和绑定 
    int *p = (int *)malloc(1000 * sizeof(int));    (可分配的内存大小是根据当前操作系统所管    
                                                    //理的内存的大小有关的)

    //第二步 : 检验申请是否成功
    if(NULL == P)
     {
        
            printf(" malloc error \n");        //进入这里说明申请分配内存空间失败

        return -1;    

     }
    
    //第三步 : 使用申请到的内存
    //p = NULL;
    //p = &a;  //如果在free之前给p另外赋值,那么malloc申请的那段内存就丢失掉了
               //malloc后p和返回的内存相绑定,p是那段内存在当前进程唯一的联系人
               //如果p在free之前就丢了,那么这段内存就永远丢了
               //丢了的概念是在操作系统的堆管理器中当前这段内存是拿着的,但是使用不了
               //只能申请新的内存来替换使用,这就叫程序“吃内存”,学名叫内存泄漏


    *(p+0) = 1;    //这样就可以通过指针访问数组,队友a[0]
    *(p+1) = 2;    //a[1]

    printf("*(p+0) = %d\n",*(p+0));
    printf("*(p+1) = %d\n",*(p+1));     //打印信息查看结果

    //第四步 : 释放
    free(p);    //直接将p传给它即可    

    p = NULL;

    return 0;

}

(1)void * 是个指针类型,malloc返回的是一个void * 类型的指针,实质上malloc返回的是堆管理器分配给我本次申请的那段内存空间的首地址(malloc返回的值其实是一个数字,这个数字表示一个内存地址) 为什么要使用void * 作为类型?主要是因为malloc帮我们分配内存时只是分配了内存空间,至于将来这段空间用来存储什么类型的元素malloc是不关心的,有我们的程序自己来决定

(2)什么是viod类型。早期被翻译成空类型,但是这个翻译不好。void类型不表示没有类型,而是表示万能类型。void类型的意思是说这个数据的类型当前是不确定的,在需要的时候可以再去指定它的具体类型。 void * 类型是一个指针类型,这个指针本身占4个字节(在linux64位机占8个字节),但是指针指向的类型是不确定的,换句话说这个指针在需要的时候可以被强制转化成其他任何一种确定类型的指针,也就是说这个指针可以指向任何类型的元素

(3)malloc的返回值:申请成功后返回这个内存空间的指针,申请失败时返回NULL,所以malloc获取的内存指针一定要先检验是否为NULL

(4)malloc申请完内存之后要free释放。 free(p);会告诉堆管理器这段内存我用完了可以回收了。堆管理器回收了这段内存后这段内存当前进程就不该使用了。因为堆管理器可能会把这段内存分配给别的进程,所以就不能再使用了

(5)在调用free归还这段内存之前,指向这段内存的指针p一定不能丢(也就是不能给p另外赋值)因为p一旦丢失了,这段malloc申请来的内存就永远丢失了(内存泄漏),直到当前程序结束操作系统才会回收这段内存

malloc的一些细节表现

        malloc(0) 

        mallo(0)申请内存本身 是一件无厘头的事情,一般不会碰到这个需要

        如果真的mallo(0)返回的是NULL还是一个有效指针?答案是:实际分配了16byte(在linux32位机中,如果是64位机的话是32byte)的一段内存并且返回了这段内存的地址。当然这个结论不是确定的,因为C语言没有明确规定malloc(0)时的表现,由个malloc函数库的实现者来定义。

        

#include <stdio.h>
#include <stdlib.h>


int main(void)
{


    int *p = (int *)malloc(0);        //先malloc(0)申请一个
    int *p1 = (int *)malloc(0);       //再malloc(0)申请一个
    
    printf("*p = %p\n",p);           //打印出第一个返回的有效指针地址 
    printf("*p1 = %p\n",p1);         //再次打印第二个指针返回的有效地址

                                     //我们可以通过计算来得到malloc(0)到底分配了多少内存空间
                                     //只要用第二次申请的减去前一个申请的就能得到结果    

    return 0;

}

        

        malloc(4)

                申请得到的还是16byte(linux32位机,64为32)的一段内存 

       在linux中 gcc编译器下32位机默认malloc分配最小内存单位为16byte(linux64位机为32),如果小于16byte都会返回16byte的内存。malloc实现时没有实现任意自己的分配而是允许一些大小块的内存的分配

       malloc(20) 去访问第25、第30、第2500 ......会怎么样?

        代码实战结果:25正确......30正确......300正确......2500正确......往后访问总会有一个数编译器开始提示段错误

七 : 数据区

        1 :代码段、数据段、bss段

                (1) 编译器在执行程序的时候,将程序中的所有的元素 分成了一些组成部分,各部分构成一个段,所以说段是可执行程序的组成部分

                (2)代码段就是程序中的可执行部分,直观理解代码段就是函数堆叠组成的。

                (3)数据段(也被称为数据区、静态数据区、静态区):数据段就是程序中的数据,直观理解就是C语言中的全局变量(注意:全局变量才算是程序中的数据,局部变量不算是程序的数据,只能算是函数的数据)

                (4)bss段(又叫ZI(zero initial)段):bss段的特点就是被初始化为0,bss本质上也是属于数据段,bss段就是被初始化为0的数据段

注意区分:数据段(.data)和bss段的区别和联系:二者本来没有本质区别,都是用来存放C语言程序中的全局变量的。区别在于把显示初始化为非零的全局变量存在.data段中,而把显示初始化为0或并未显示初始化(C语言规定未显示初始化的全局变量的值默认为0)的全局变量存在bss段

        2 :有些特殊数据会被放在代码段

                (1)C语言使用 char *p = “linux”; 定义字符串“linux”实际上被分配在代码段,也就是说这个字符串“linux”其实是一个常量字符串而不是变量字符串

#include <stdio.h>


int main(void)
{

    int *p = "linux";

    *(p+0) = 'f';      //按道理来说这样是将"linxu"这个字符串的第一个字符改成f
                       //并且编译没警告无错误
                       //但是当执行程序时编译器返回段错误
    
    printf("*p = %s\n",*p);

    return 0;

}

                (2)const型常量:在C语言中const关键字用来定义常量,常量就是不能被改变的量 

                        const的方法实现至少有两种:第一种是编译将const修饰的变量放在代码段去以实现不能修改(普遍见于各种单片机的编译);第二种就是由编译器来检查确保const型的常量不会被修改,实际上const型的常量还是和普通变量一样被放在数据段的(gcc中就是这样实现的)

3 :显示初始化为非零的全局变量和静态局部变量放在数据段

                (1)放在.data段的变量有两种:第一种是显示初始化为零的全局变量

#include <sdio.h>


int a = 5;        //显示初始化为非零的变量(全局变量)分配在数据段


int main(void)
{

    
    printf(" a = %p \n",&a);

    return 0;

}

第二种是静态局部变量,也就是static修饰的变量 (普通局部变量放在栈上面,静态局部变量分配在.data段上)

3 :未初始化或显示初始化为零的全局变量放在bss段

        (1)bss段和 .data段并没有本质区别,几乎可以不用明确去区分

总结 :C语言所有变量和常量所使用的内存无非三种情况

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char str[] = "linux";               //第二种 全局变量 数据段中 生命周期程序开开始到程序结束        
                                    //消灭

int main(void)
{
    
     char a[] = "linux";            //第一种 定义局部变量 栈上 生命周期函数内部作用
     char *p = (char *)malloc(10);    
    if(p==NULL)
        {


            printf("malloc error\n");

            return -1;

        }

    memset(p,0,10);    //第三种 放在申请malloc的堆内存中 生命周期 malloc申请到free释放
    strcpy(p,"linux");

    printf(" a = %s\n",a);
    printf(" p = %s\n",p);
    printf(" str = %s\n",str);

    free(p);

    return 0;


}

        (1) 相同点:三种获取内存的方法,都可以给程序提供可用内存,都可以用来定义变量给程序使用

        (2)不同点:栈对应c中的普通局部变量(别的变量用不了栈,而且栈是自动的,由编译器和运行环境共同来提供服务,程序员无法手工控制);堆内存完全是独立于我们的程序存在和管理的,程序需要内存时可以去手工申请malloc,使用完成后必须尽快free释放;数据段对于程序来说对应c程序中的全局变量和静态局部变量

        (3)如果需要一段内存来存储数据。那么究竟应该把数据存储在哪里(或者说要定义一个变量,究竟是要定义为局部变量还是全局变量或者用malloc来实现)。不同的存储方式有不同的特点,简单总结如下:

        * 函数内部临时使用,出了函数不会用到,就定义局部变量

        * 堆内存和数据段几乎完全拥有相同的属性,大部分时候是可以完全替换的但是生命周期不一样。堆内存的生命周期是从malloc申请到free释放结束,全局变量是从程序开始执行到程序结束才会消灭,伴随程序运行的一生 启示:如果要定义一个变量,这个变量只是在一个阶段有用,用完就不用了,就适合用堆内存。如果这个变量本身和程序是一生相伴的,那就适合用全局变量

  • 49
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值