0
点赞
收藏
分享

微信扫一扫

冒牌程序员-毛哥 C语言入门教程(第四章:程序流 第十三节 内存)

第十三节:内存的使用

程序内存使用可以分为三类:

1. 可执行程序从磁盘装入内存中的时候,系统为可执行程序分配的内存。

2. 程序执行过程中,使用的临时变量,临时信心(例如函数返回地址)等所占用的内存-栈。

3. 程序在执行过程中,通过系统调用,请求操作系统动态的分配和释放的内存(malloc和free)-堆。

第一种内存和第二种内存,每个程序都会用到。第三种内存可能用到,当然可能也用不到。我们在编写我们自己程序的时候,一定要明白,我们所写代码,用的是哪一种内存,这样对于我们后续学习,会打下坚实的基础。

对于我们所写的C代码,有这么几种语句:执行语句,变量定义语句。其中执行语句肯定会生成执行代码,那么一定使用的是第一种内存。变量的定义中,全局变量和静态变量,使用的也是第一种内存,这个原因在前面讲过,因为这种变量在程序执行前必须准备好,且在程序执行期间都有效。第二种内存,也就是栈,主要是临时变量使用这部分内存;另外在函数调用的过程中,函数返回地址也使用这部分内存;后面这句大家可能不理解,知道有这么回事就可以了,就是函数每次调用都产生一个调用栈帧(stack frame),一般都这么叫的,所谓栈帧,其实就是函数变量,函数返回地址组,还有函数的临时变量成的这么一堆数据,这对数据的地址被一个指针变量保存在内存中,这个指针变量叫栈帧指针,这个也被保存在栈中。栈大部分内存都被函数调用栈帧占用。大家现在可以这么理解,一个程序执行序列,必须有一个栈和这个程序执行序列配合。当程序被装入内存,可以产生一个或者多个程序执行序列,每个程序执行序列,就配备一个栈,这个栈记录了这个程序执行序列的函数调用顺序,例如main调用printf,当printf返回的时候,调用printf的栈帧信息就销毁,然后你在调用scanf函数,那么调用scanf函数的同时,在栈内就产生调用scanf函数的记录,也就是调用栈帧。在scanf函数执行期间,这个scanf调用栈帧一直存在。如果scanf再调用其他函数,又会产生一个其他函数的调用栈帧。第三种内存的使用,必然在程序中出现类似malloc函数调用,这类函数调用的结果是分配一段第三类内存,也就是堆内存,这类内存可以被随时释放掉(free)。

注意:

预编译指令不产生代码,只是进行程序中的文本替换。替换好的文本,在编译的过程中,有可能使用内存。

下面用一个程序,对上面的描述进行说明:

/**********冒牌程序员-毛哥************/

#include<stdio.h>
//这一句不会产生任何代码,#include是预处理命令,只是用stdio.h文件内容,替换这句话。

int global=100;
//这是一个全局变量,这个变量信息会被记录到编译好的可执行程序中,也就是程序运行的时候,
//可执行程序被拷贝到内存,因此是第一类内存

/*
编译器会在程序中生成一段名字叫printf_count的子程序,子程序的指令有print_count函数的
函数体源代码产生。
*/
void print_count(void)
{
    static int count=1;
    //这个是一个静态变量,其性质和全局变量一样,只不过在程序编写过程中,只允许在本函数内
    //使用这个变量。

    count++; //这句话会产生count变量加一的程序执行指令。
    printf("static count = %d\n",count);
    /*
    这一句会产生一个函数调用指令。
    当程序执行的时候,这一句会使用第二类内存,也就是栈内存,函数调用指令会在栈内存中存放
    "static count = %d\n",这个字符串的指针,以及count变量的值。这些都是临时信息,当
    函数printf返回时,这些临时信息会从栈内被清除。
    */
}

/*
这个main函数有了函数参数,第一个函数参数是一个int类型的变量,第二个参数是一个char类型的指针
数组,其实就是一个数组,数组中每个元素都是一个char类型的指针。数组元素的个数就是第一个参数的argc
的值。

这两个参数的名字可以任意取,和其他函数的参数性质一样,但一般由于main函数的特殊性,因此这两个
函数参数的名字基本是固定的。

argv这个数组的每个元素,是一个字符串指针,指向一个字符串,因此可以通过字符串传递给main函数一堆参数。

注意,这俩参数也是通过栈传递的,使用的是第二类内存,argc和argv属于临时变量,只有在main函数的函数
体内能够使用。

*/
int main(int argc,char *argv[])
{
    for(int i=0;i<argc;i++)//int i=0说明i是for循环体中的临时变量,只能在for循环体内使用。
    {
        printf("%s\n",argv[i]);
    }
    /*
    通过以上输出可以看见,传递给main函数的第一个参数是程序的名字,剩下的就是我们通过命令行,传递
    给程序的命令行参数。这个命令行参数没有任何格式要求,但一般我们应该符合命令行参数格式。
    */

    int *ip=malloc(0x100);
    //使用malloc动态分配一段内存,这属于堆内存,也就是第三种内存的使用方式。
    //内存也属于系统资源,其实malloc也是一个使用操作系统功能的函数。当然,函数调用
    //会产生栈内存的使用,0x100和函数的返回地址,都被保存在栈内。

    for(int i=0;i<0x100;i++)
    {
        ip[i]=i+1;
    }

    free(ip);   //使用完毕后,记得释放内存。

    return 0;
}

现在,我们只是大概掌握了C语言编写的源程序和可执行程序,以及可执行程序在执行过程中对内存,磁盘的使用情况,但这些数据或者代码其实存在一定的格式的。在windows中,可执行程序的格式我们叫做PE格式,在Linux中可执行程序的格式叫ELF格式,这是两种不同的可执行程序接口,又叫做二进制接口,同意叫做ABI,注意和API进行区分。(ABI application binary interface-应用程序二进制接口, API-application interface,一般出现在window操作系统中)。

如果要精通C语言,那么windows的PE格式,和linux的ELF格式必须清楚,这是我们后续课程的一个内容。在学习此内容前,我们先要学习汇编语言和计算机原理,然后是系统编程,windows中叫windows API编程,linux中叫linux系统编程。

现在我们可以进行数据结构的学习以及计算机原理和汇编语言的学习了。


举报

相关推荐

0 条评论