|
C程序中经常需要使用各种变量,如全局变量、静态变量、局部变量,编程时需要了解这些变量的作用域及其所占用的存储位置及存储空间大小,包括静态存储和动态存储的概念。若程序中的一个变量在运行时总是不正常地被改变,那么有理由怀疑它临近的数据存在溢出情况,从而改变了这个变量值。要进行跟踪排查,就必须知道该变量被分配的位置及其附近的其他变量。
|
|
|
程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是通常所说的全局变量。
|
|
|
例如,在下面的程序段中,有全局变量degree、cnt和局部变量times、price。
|
|
|
|
|
一个C程序在不同的系统中运行时,虽然对其代码和数据所占用的内存空间会有不同的布局和安排,但是一般都包括正文段(包含代码和只读数据)、数据区、堆和栈等。例如,在Linux系统中进程的内存布局示意图如下图所示。
|
|
|
|
|
(1)正文段中主要包括由CPU执行的机器指令,该存储区是只读区域,以防止程序由于意外事件而修改,该段也是可共享的,因此经常执行的程序在存储器中只需要有一个副本。
|
|
|
(2)数据区(段)分为初始化部分和未初始化部分,在程序中已初始化的全局变量和静态局部变量的存储单元在该区域。还有程序中未初始化的全局数据所占存储区域,常称为BSS段(来源于早期汇编程序的一个操作,即Block Started by Symbol),在程序开始执行之前,内核将此段初始化为0。
|
|
|
(3)栈是局部变量以及每次函数调用时所需保存的信息的存储区域,其空间的分配和释放由操作系统进行管理。每次函数调用时,其返回地址以及调用者的环境信息(例如某些寄存器)都存放在栈中。然后,在栈中为新被调用的函数的自动和临时变量分配存储空间。栈空间向低地址方向增长。
|
|
|
(4)堆是一块动态存储区域,由程序员堆分配和释放,若程序员不释放,则程序结束时由操作系统回收。堆空间地址的增长方向是从低地址向高地址。在C程序中,通过调用标准库函数malloc/calloc/realloc等向系统动态地申请堆存储空间来存储相应规模的数据,之后用free函数释放所申请到的存储空间。
|
|
|
当程序使用这些函数去获得新的内存空间时,系统首先在堆上进行内存空间的分配,操作系统一般需要维护一个记录空闲内存地址的链表。当系统收到程序的申请时,会遍历该链表,寻找适用于所申请空间大小的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给用户程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free操作才能正确地释放本段内存空间。由于找到的堆结点大小不一定正好等于申请空间的大小,因此涉及到复杂的分配机制,需要进行系统调用,可能产生内存碎片以及用户态与核心态的转换等一系列问题。
|
|
|
对于内存受限的系统,应尽量避免使用动态内存分配,多采用静态内存分配,从而在程序编译时就能确定其运行时所需要的存储空间。
|
|
|
|
在计算机系统中是以字节为单位存储信息的,每个地址单元都对应着一个字节(8bit)。但是在C程序中除了8bit的char型数据外,还有16bit的short型、32bit的int型及long型(要看具体的编译器)。另外,对于16位或者32位的处理器,由于寄存器宽度为多个字节,那么必然存在着如何将多个字节安排的问题。因此就导致了大端(Big-endian)存储模式和小端(Little-endian)存储模式。
|
|
|
大端模式就是高位字节存储在内存的低地址端,低位字节存储在内存的高地址端。
|
|
|
小端模式就是低位字节存储在内存的低地址端,高位字节存储在内存的高地址端。
|
|
|
常用CPU中的PowerPC、IBM、Sun、KEIL C51采用大端模式,X86、DEC采用小端模式,很多ARM、DSP为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
|
|
|
一般操作系统是小端模式,而通信协议是大端模式。另外,Java和所有的网络通信协议都是使用大端模式的编码。
|
|
|
例如,对于一个32bit的十六进制整数0x12345678,在Little-endian模式以及Big-endian模式内存中的存储方式(假设从地址0x4000开始存放)如下表所示。
|
|
|
|
|