C程序的构成及动态内存分配

对一个程序,通常的理解就是,源码编译成机器代码,然后通过机器解释运行。不过是怎样编译成机器代码,和怎样运行的,无疑是个值得探讨的问题。怎样编译成机器代码,过程就是源码的编译、链接,编译器做了这些事。而怎样运行,却不是哪个器件自己一己之力就可以做到的。机器代码要在机器上运行,就得要请求硬件资源。涉及最多的就是CPU和内存了。CPU进行逻辑控制和运算,内存用于运行过程中的数据的快速交互场所。

一个C程序从其自身代码的结构上来看,编译过后不过是一段代码。而这段代码,从磁盘系统加载到内存中被称为代码段或正文段(code/text segment)的地方。在内存中的正文段是共享的。所以,当我们运行同一程序的多个进程时,在内存中,只有一个程序代码的副本。当然,为了防止有人要做坏事,正文段的权限常常都是只读的。

一个只有谋略,而手下无兵的将领,在战场上是什么也做不了的。程序代码,当然也不可能纯粹的都是逻辑描述。还必须得有逻辑作用的对象和结果——数据。也即程序中的各种变量。不同变量,在程序中的地位看起来除了作用域与生存周期外,都大同小异。不过,它们的实现确实非常的不同的。

程序中的未初始化的全局变量,存储在一个被称为未初始化数据段的地方。也即是bbs(block started by symbol)段。同时,内核会自动的将此段中的数据初始化为0或空指针。如,函数外的声明:

int sum[10];

使得该变量存放在bbs段中。

既然有了未初始化的全局变量,当然也就有初始化了的全局变量。它们存放在一个称为初始化数据段(常简称为数据段)的地方。拥有全局的作用域整个程序的生存周期。(Right?) 如,任函数外的声明:

int tmp = 99;

使得该变量存放在数据段中。

当然,还有一个叫堆栈段的东西。自动变量及每次函数调用是所需要保存的信息都存放在这里。堆栈的特点是先进后出(FILO)及数据存放的周期短,即进栈出栈操作很频繁。自动变量通常都是作为临时变量存在的,存在周期短,非常时候放在栈中。(Right?) 而函数调用时的的现场信息,可以利用堆栈的先进先出特点很方便的进行保护和恢复。典型的,递归的实现就是用了堆栈进行,因为堆栈是一层层向下增长的,所以在子函数中是不会覆盖调用函数的参数的。特别得要注意的一点是,主函数main中的变量,也是自动变量。因为主函数也是函数啊!

最后,作为C和C++特有的动态内存分配。是在运行时,利用堆来动态的进行内存分配的。所以动态分配的内存都具有全局的作用域(从分配后开始)。而生存周期而是直到其被释放前。动态内存的分配,其实就是向内核请求一块内存资源,而释放呢,就是将资源返还给内核。所以动态分配的内存都必须在不再需要时释放。不然可能会造成内存泄露及动态分配失败等恼人的问题。

看到这里,我们可以发现。一个C程序,其代码是被加载到了内存的代码段中,而其代码段中的一个个的变量,并没有实际的存放数据,数据根据情况的不同存放在了bbs段,数据段,堆栈段及堆中,代码段中的变量,存放的是一个指向各个实际存放地方的指针。(Right?)

典型的C内存分布图:

C程序的构成及动态内存分配

再说说C中动态内存管理。在C中,主要由标准头文件<stdlib.h>中定义的几个函数来进行内存管理:

void* malloc(size_t size)

void* calloc(size_t nobj, size_t size)

void* realloc(void *p, size_t size)

void* free(void *p)

上面这四个函数,都返回一个void* 指针。在C中,void* 指针可以接收任意类型指针,同时,可以不经强制类型转换直接传递给任意类型指针。而在C++中,前一种情况一样,后一种情况必须进行强制类型转换。

先说说malloc,它接收一个size_t 类型的参数作为请求的内存的大小,以字节为单位。所以,在内存分配中,通常会用到sizeof运算符来获取要分配的数据类型的大小。如:

int *p = malloc(sizeof(int));

为一个int类型的指针 p 分配了一段内存区域。

calloc函数,用于为一个数组对象分配内存,第一个参数为数组的大小,第二个参数为每中数据类型的大小。一般calloc用得不多,可以直接用malloc替代。

realloc函数,用于调整已分配内存的大小。当你发现你的当前内存太多或太小时,就可以用realloc来进行内存的调整了。

所有这三个内存分配函数失败时都会返回NULL,所以可以通过检查返回值来判断内存分配是否成功。

最后,free函数,用于释放使用上述三个函数动态分配的内存。并且必须进行释放。

C中的内存分配函数的实现是由系统来完成的。所以不同系统会有不同的实现。UNIX-like系统中,一般是由sbrk系统调用来实现的。(更具体?)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/3b7e696e602fc2f5214230f4d6af91b5.html