C语言编程程序的内存如何布局

在c语言中,每个变量和函数有两个属性:数据类型数据的存储类别
C语言中局部变量和全局变量变量的存储类别(static,extern,auto,register)

1. 从变量的作用域划分变量(即从空间)角度来分

1.全局变量
2.局部变量

2. 从变量值存在的时间或存储类别(即生存期)角度来分

2.1. 静态存储区

存放以下数据: 代码段(text)只读数据段(rodata)读写数据段(rwdata)未初始化数据段(bbs)

静态存储区存放全部的全局变量, 这些变量将在链接之后产生, 程序执行完毕就释放, 程序执行的过程中它们占据固定的存储单元, 而不会动态的进行分配和释放

2.2. 动态存储区

存放以下数据: 函数形参自动变量(未加static声明的局部变量)函数调用时的现场保护和返回地址

对以上这些数据,在函数开始调用时分配动态存储空间,函数结束时释放这些空间。

3. 从用户内存空间角度分为三个部分

1. 程序区
一行一行的等待执行的机器码 
2. 静态存储区
3. 动态存储区

4. 从C程序运行时又可分为以下存储区

1. 代码段 (Code | Text)

代码段由程序中执行的机器代码组成。在C语言中,程序语句进行编译后,形成机器代码。在执行程序的过程中,CPU的程序计数器指向代码段的每一条机器代码,并由处理器依次运行。

2. 只读数据段(ROData)

2.1 ROData介绍
  1. 只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。
  2. 只读数据段由程序中所使用的数据产生,该部分数据的特点是在运行中不需要改变,因此编译器会将该数据段放入只读的部分中。C语言中的只读全局变量只读局部变量,程序中使用的常量等会在编译时放入只读数据区
  3. 注意:定义全局变量const char a[100]={“ABCDEFG”};将生成大小为100个字节的只读数据区,并使用“ABCDEFG”初始化。如果定义为:const char a[ ]={“ABCDEFG”};则根据字符串长度生成8个字节的只读数据段(还有’\0’),所以在只读数据段中,一般都需要做完全的初始化。
2.2 Example
#define A=18; ##常量

const int A = 18;     ##只读全局变量
int main(){
    const int B = 18;     ##只读局部变量
}

3. 已初始化读写数据段(RW data)

3.1 RWData介绍


  1. 已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并具有初值,以供程序运行时读写。
  2. 全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区程序行完毕就释放。在程序执行过程中它们占据固定的存储单元而不动态地进行分配和释放

全局变量
静态(static) 局部变量

3.2 Example
int global_init_val=1;                   ## 全局变量
int main(int argc, char * argv[]){
    static int a=1;                      ## 静态(static) 局部变量
}

4. 未初始化数据段(BSS)

4.1 BSS介绍

未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。

4.1 Example
int global_noinit_val;                   ## 全局未初始化全局变量
char *p1;                                ## 全局未初始化全局变量

int main(int argc, char * argv[]){
    ......                      
}

5. 堆(heap)

5.1 堆空间介绍

堆内存只在程序运行时出现,一般由程序员分配释放
在具有操作系统的情况下,如果程序没有释放操作系统可能在程序(例如一个进程)结束后回收内存

5.2 Example
p1 = (char*) malloc(10);     ## 分配得来的10和20个字节的区域就在堆区
p2 = (char*) malloc(20);

6. 栈(stack)

6.1 栈空间介绍
  1. 栈内存只在程序运行时出现,在函数内部使用的变量函数的参数以及返回值使用栈空间,
  2. 栈空间由编译器自动分配和释放。
  3. 栈空间动态开辟回收的。在函数调用过程中,如果函数调用的层次比较多,所需要的栈空间也逐渐加大
  4. 对于参数的传递返回值,如果使用较大的结构体,在使用的栈空间也会比较大
6.2 栈区主要用于以下数据的存储
  1. 函数内部的动态变量
  2. 函数的参数
  3. 函数的返回值
6.3 Example
void main(void){
  int b;                    ## 栈
  char s[] = "abc";         ## 栈
  char *p2;                 ## 栈
  char *p3 = "123456";      ## 123456\0在常量区 ## p3 在栈上。
}


===================================华丽的分割线=========================

5. 4种局部变量和全局变量的存储类别(static, extern, auto, register)

5.1 Static

有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为“静态局部变量”,用关键字static进行声明。

int f(int a)
{
    auto int b=0;
    static int c=3;
    b=b+1;
    c=c+1;
    return(a+b+c);
}
int main(void)
{
    int a=2,i;
    for(i=0;i<3;i++)
         printf("%d",f(a));
}

对静态局部变量的说明:

1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放。
2)静态局部变量在编译时赋初值,即只赋初值一次;而对自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
3)如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。

5.2 Extern

外部变量即全局变量)是在函数的外部定义的,它的作用域为从变量定义处开始,到本程序文件的末尾。如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。如果在定义点之前的函数想引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”。表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量

int max(int x,int y)
{
    int z;
    z=x>y?x:y;
    return(z);
}
int main(void)
{
    extern A,B;
    printf("%d\n",max(A,B));
}
int A=13,B=-8;

说明:

在本程序文件的最后1行定义了外部变量A,B,但由于外部变量定义的位置在函数main之后,因此本来在main函数中不能引用外部变量A,B。现在我们在main函数中用extern对A和B进行“外部变量声明”,就可以从“声明”处起,合法地使用该外部变量A和B。

5.3 Auto

函数中的局部变量,如不专门声明为static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。

int f(int a)         /*定义f函数,a为参数*/
{
    auto int b,c=3;     /*定义b,c自动变量*/
}

a是形参,b,c是自动变量,对c赋初值3。执行完f函数后,自动释放a,b,c所占的存储单元。
关键字auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。 占用栈空间

5.4 Register

为了提高效率,C语言允许将局部变量得值放在CPU中的寄存器中,这种变量叫“寄存器变量”,用关键字register作声明。

int fac(int n)
{
    register int i,f=1;
    for(i=1;i<=n;i++)
        f=f*I;
     return(f);
}
int main(void)
{
    int i;
    for(i=0;i<=5;i++)
        printf("%d!=%d\n",i,fac(i));
}

说明:

1) 只有局部自动变量和形式参数可以作为寄存器变量
2)一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量
3)局部静态变量不能定义为寄存器变量

6. 总结

  1. 从变量的作用域(即从空间)角度来,可以分为全局变量局部变量
  2. 从变量值存在的作时间(即生存期)角度来,可以分为静态存储方式和动态存储方式。
  3. 代码段只读数据段读写数据段未初始化数据段属于静态区域

    静态区域: 是指在程序运行期间分配固定的存储空间的方式

  4. 属于动态区域

    动态区域: 是在程序运行期间根据需要进行动态的分配存储空间的方式。

  5. 代码段只读数据段读写数据段在链接之后产生

  6. 未初始化数据段在程序初始化的时候开辟
  7. 在程序的运行中分配和释放
  8. C语言程序分为映像运行时两种状态。在编译-连接后形成的映像中,将只包含代码段(Text)只读数据段(RO Data)读写数据段(RW Data)
  9. 程序运行之前,将动态生成未初始化数据段(BSS)
  10. 程序的运行时还将动态形成堆(Heap)区域和栈(Stack)区域。一般来说,在静态的映像文件中,各个部分称之为节(Section),而在运行时的各个部分称之为段(Segment)。如果不详细区分,可以统称为
  11. C语言在编译和连接后,将生成代码段(Text)、只读数据段(RO Data)和读写数据段(RW Data)。在运行时,除了以上三个区域外,还包括未初始化数据段(BSS)区域和堆(Heap)区域和栈(Stack)区域。

7. 一些实例

   const char ro[ ] = {"this is read only data"}; //只读数据区

  static char rw_1[ ] ={"this is global read write data"}; //已初始化读写数据段

  char BSS_1[ 100]; //未初始化数据段

  const char *ptrconst ="constant data"; //字符串放在只读取数据段

  int main()

  {

      short b; //在栈上,占用2个字节

      char a[100]; //在栈上开辟100个字节, 它的值是其首地址

      char s[ ]="abcdefg"; //s在栈上,占用4个字节,"abcdefg"本身放置在只读数据存储区,占8个字节

      char *p1; //p1在栈上,占用4个字节

      char *p2="123456"; //p2 在栈上,p2指向的内容不能改,“123456”在只读数据区

      static char rw_2[ ]={"this is local read write data"};//局部已初始化读写数据段

      static char BSS_2[100]; //局部未初始化数据段

      static int c = 0; //全局(静态)初始化区

      p1=(char *)malloc(10 * sizeof(char ) ); //分配内存区域在堆区

      strcpy(p1,"xxxx"); //“XXXX”放在只读数据区,占5个字节

      free(p1); //使用free释放p1所指向的内存

      return 0;

  }

  读写数据段包含了忆初始化的全局变量 static char rw_1[ ]以及局部静态变量static rw_2[ ].其差别在于编绎时,是在函数内部使用的还是可以在整个文件中使用。对于rw_1[] 无论有无static 修饰,其都将被放置在读写数据区,只是能否被其它文件引用与否。对于后者就不一样了,它是局部静态变量,放置在读写数据区,如果没static修饰,其意义完全改变,它将会是开辟在栈空间的局部变量,而不是静态变量,在这里rw_1[],rw_2[]后没具体数值,表示静态区大小同后面字符串长度决定。

  对于未初始化数据区BSS_1[100]与BSS_2[100],其区别在于前者是全局变量,在所有文件中都可以使用;后者是局部变量,只在函数内部使用。未初始化数据段不设置后面的初始化数值,因此必须使用数值指定区域的大小,编绎器将根据大小设置BSS中需要增加的长度。

参考文章
深入探讨C语言中局部变量与全局变量在内存中的存放位置
C语言编程程序的内存如何布局

你可能感兴趣的:(C语言编程程序的内存如何布局)