一.C语言程序的存储区域
1.由C语言代码(文本文件)形成可执行程序(二进制文件),需要经过编译-汇编-连接三个阶段。编译过程把C语言文本文件生成汇编程序,汇编过程把汇编程序形成二进制机器代码,连接过程则将各个源文件生成的二进制机器代码文件组合成一个文件。
2.C语言编写的程序经过编译-连接后,将形成一个统一文件,它由几个部分组成。在程序运行时又会产生其他几个部分,各个部分代表了不同的存储区域:
1>代码段(Code或Text)
代码段由程序中执行的机器代码组成。在C语言中,程序语句执行编译后,形成机器代码。在执行程序的过程中,CPU的程序计数器指向代码段的每一条机器代码,并由处理器依次运行。
2>只读数据段(RO data)
只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。
3>已初始化读写数据段(RW data)
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并且有初值,以供程序运行时读写。
4>未初始化数据段(BBS)
未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。
5>堆(heap)
堆内存只在程序运行时出现,一般由程序员分配和释放。在具有操作系统的情况下,如果程序没有释放,操作系统可能在程序(例如一个进程)结束后会后内存。
6>栈(statck)
堆内存只在程序运行时出现,在函数内部使用的变量,函数的参数以及返回值将使用栈空间,栈空间由编译器自动分配和释放。
3.代码段、只读数据段、读写数据段、未初始化数据段属于静态区域,而堆和栈属于动区域。代码段、只读数据段和读写数据段将在连接之后产生,未初始化数据段将在程序初始化的时候开辟,而对堆和栈将在程序饿运行中分配和释放。
4.C语言程序分为映像和运行时两种状态。在编译-连接后形成的映像中,将只包含代码段(Text)、只读数据段(R0 Data)和读写数据段(RW Data)。在程序运行之前,将动态生成未初始化数据段(BSS),在程序的运行时还将动态生成堆(Heap)区域和栈(Stack)区域。
注:1.一般来说,在静态的映像文件中,各个部分称之为节(Section),而在运行时的各个部分称之为段(Segment)。如果不详细区分,统称为段。
2.C语言在编译连接后,将生成代码段(TEXT),只读数据段(RO Data)和读写数据段(RW
Data)。在运行时,除了上述三个区域外,还包括未初始化数据段(BBS)区域和堆(heap)区域和栈(Stack)区域。
二.C语言程序的段
1.段的分类
每一个源程序生成的目标代码将包含源程序所需要表达的所有信息和功能。目标代码中各段生成情况如下:
1>代码段(Code)
代码段由程序中的各个函数产生,函数的每一个语句将最终经过编译和汇编生成二进制机器代码
2>只读数据段(RO Data)
只读数据段由程序中所使用的数据产生,该部分数据的特点在运行中不需要改变,因此编译器会将数据放入只读的部分中。C语言的一些语法将生成只读数据数据段。
² 只读数据段(RO Data)
只读数据段(RO Data)由程序中所使用的数据产生,该部分数据的特点是在运行中不需要改变,因此编译器会将数据放入只读的部分中。以下情况将生成只读数据段。
n 只读全局变量
定义全局变量const char a[100]=”abcdefg”将生成大小为100个字节的只读数据区,并使用字符串“abcdefg”初始化。如果定义为const char a[]=”abcdefg”,没有指定大小,将根据“abcdefgh”字串的长度,生成8个字节的只读数据段。
n 只读局部变量
例如:在函数内部定义的变量const char b[100]=”9876543210”;其初始化的过程和全局变量。
n 程序中使用的常量
例如:在程序中使用printf("information\n”),其中包含了字串常量,编译器会自动把常量“information \n”放入只读数据区。
注:在const char a[100]={“ABCDEFG”}中,定义了100个字节的数据区,但是只初始化了前面的8个字节(7个字符和表示结束符的‘\0’)。在这种用法中,实际后面的字节米有初始化,但是在程序中也不能写,实际上没有任何用处。因此,在只读数据段中,一般都需要做完全的的初始化。
3.读写数据段(RW Data)
读写数据段表示了在目标文件中一部分可以读也可以写的数据区,在某些场合它们又被称为已初始化数据段。这部分数据段和代码,与只读数据段一样都属于程序中的静态区域,但是具有科协的特点。
n 已初始化全局变量
例如:在函数外部,定义全局的变量char a[100]=”abcdefg”
n 已初始化局部静态变量
例如:在函数中定义static char b[100]=”9876543210”。函数中由static定义并且已经初始化的数据和数组将被编译为读写数据段。
说明:
读写数据区的特点是必须在程序中经过初始化,如果只有定义,没有初始值,则不会生成读写数据区,而会定义为未初始化数据区(BSS)。如果全局变量(函数外部定义的变量)加入static修饰符,写成static char a[100]的形式,这表示只能在文件内部使用,而不能被其他文件使用。
4.未初始化数据段(BSS)
未初始化数据段常被称之为BSS(英文名为Block start by symbol的缩写)。与读写数据段类似,它也属于静态数据区。但是该段中数据没有经过初始化。因此它只会在目标文件中被标识,而不会真正称为目标文件中的一个段,该段将会在运行时产生。未初始化数据段只有在运行的初始化阶段才会产生,因此它的大小不会影响目标文件的大小。
三.在C语言的程序中,对变量的使用还有以下注意
1.在函数体中定义的变量通常是在栈上,不需要在程序中进行管理,由编译器处理。
2.用malloc,calloc,realoc等分配分配内存的函数所分配的内存空间在堆上,程序必须保证在使用后使用后freee释放,否则会发生内存泄漏。
3.所有函数体外定义的是全局变量,加了static修饰符后的变量不管在函数内部或者外部存放在全局区(静态区)。
4.使用const定义的变量将放于程序的只读数据区。
说明:
在C语言中,可以定义static变量:在函数体内定义的static变量只能在该函数体内有效;在所有函数体外定义的static变量,也只能在该文件中有效,不能在其他源文件中使用;对于没有使用static修饰的全局变量,可以在其他的源文件中使用。这些区别是编译的概念,即如果不按要求使用变量,编译器会报错。使用static 和没使用static修饰的全局变量最终都将放置在程序的全局去(静态去)。
四.程序中段的使用
C语言中的全局区(静态区),实际上对应着下述几个段:
只读数据段:R0 Data
读写数据段:RW Data
未初始化数据段:BSS Data
一般来说,直接定义的全局变量在未初始化数据区,如果该变量有初始化则是在已初始化数据区(RW Data),加上const修饰符将放置在只读区域(R0 Data).
eg:
const char ro[]=”this is a readonlydata”;//只读数据段,不能改变ro数组中的内容,ro存放在只读数据段。
char rw1[]=”this is global readwrite data”;//已初始化读写数据段,可以改变数组rw1中的内容。应为数值是赋值不是把”this is global readwrite data” 地址给了rw1,不能改变”this is global readwrite data”的数值。因为起是文字常量放在只读数据段中
char bss_1[100];//未初始化数据段
const char *ptrconst = “constant data”;//”constant data”放在只读数据段,不能改变ptrconst中的值,因为其是地址赋值。ptrconst指向存放“constant data”的地址,其为只读数据段。但可以改变ptrconst地址的数值,因其存放在读写数据段中。
int main()
{
short b;//b放置在栈上,占用2个字节
char a[100];//需要在栈上开辟100个字节,a的值是其首地址
chars[]=”abcde”;//s在栈上,占用4个字节,“abcde”本身放置在只读数据存储区,占6字节。s是一个地址常量,不能改变其地址数值,即s++是错误的。
char*p1;//p1在栈上,占用4个字节
char*p2 =”123456”;//”123456”放置在只读数据存储区,占7个字节。p2在栈上,p2指向的内容不能更改,但是p2的地址值可以改变,即p2++是对的。
static char bss_2[100];//局部未初始化数据段
static int c=0 ;//局部(静态)初始化区
p1 = (char *)malloc(10*sizeof(char));//分配的内存区域在堆区
strcpy(p1,”xxx”);//”xxx”放置在只读数据存储区,占5个字节
free(p1);//使用free释放p1所指向的内存
return 0;
}
说明:
1.只读数据段需要包括程序中定义的const型的数据(如:const char ro[]),还包括程序中需要使用的数据如“123456”。对于const char ro[]和const char * ptrconst的定义,它们指向的内存都位于只读数据据区,其指向的内容都不允许修改。区别在于前者不允许在程序中修改ro的值,后者允许在程序中修改ptrconst本身的值。对于后者,改写成以下的形式,将不允许在程序中修改ptrconst本身的值:
const char * const ptrconst = “const data”;
2.读写数据段包含了已经初始化的全局变量static char rw1[]以及局部静态变量static char
rw2[]。rw1和rw2的差别在于编译时,是在函数内部使用的还是可以在整个文件中使用。对于前者,static修饰在于控制程序的其他文件时候可以访问rw1变量,如果有static修饰,将不能在其他的C语言源文件中使用rw1,这种影响针对编译-连接的特性,但无论有static,变量rw1都将被放置在读写数据段。对于后者rw2,它是局部的静态变量,放置在读写数据区;如果不使用static修饰,其意义将完全改变,它将会是开辟在栈空间局部变量,而不是静态变量。
3.未初始化数据段,事例1中的bss_1[100]和bss_2[200]在程序中代表未初始化的数据段。其区别在于前者是全局的变量,在所有文件中都可以使用;后者是局部的变量,只在函数内部使用。未初始化数据段不设置后面的初始化数值,因此必须使用数值指定区域的大小,
编译器将根据大小设置BBS中需要增加的长度。
4.栈空间包括函数中内部使用的变量如short b和char a[100],以及char *p1中p1这个变量的值。
1》变量p1指向的内存建立在堆空间上,堆空间只能在程序内部使用,但是堆空间(例如p1指向的内存)可以作为返回值传递给其他函数处理。
2》栈空间主要用于以下3类数据的存储:
a.函数内部的动态变量
b.函数的参数
c.函数的返回值
3》栈空间主要的用处是供函数内部的动态变量使用,变量的空间在函数开始之前开辟,在函数退出后由编译器自动回收
4.看一个事例:
#include<stdio.h>
int main()
{
char*p =”tiger”;
p[1]=’I’;
p++;
printf(“%s\n”,p);
}
编译后提示:段错误
分析:
char *p =”tiger”;系统在栈上开辟了4个字节存储p的数值。”tiger”在只读存储区中存储,因此”tiger”的内容不能改变,*p=”tiger”,表示地址赋值,因此,p指向了只读存储区,因此改变p指向的内容会引起段错误。但是因为p是存放在栈上,因此p的数值是可以改变的,因此p++是正确的。
五.const的使用
1.前言:
const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程序上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解被人的程序有所帮助。
2.const变量和 常量
(1)const修饰的变量,其值存放在只读数据段中,起值不能被改变。称为只读变量。
其形式为 const int a=5;此处可以用a代替5.
(2)常量:其也存在只读数据段中,其数值也不能被改变。其形式为”abc”,5.
3.const 变量和const限定的内容
先看一个事例:
#include<stdio.h>
typedef char *pStr;
intmain()
{
char string[6] = “tiger”;
const char*p1 = string;
const pStr p2 = string;
p1++;
p2++;
printf(“p1=%s\np2=%s\n”,p1,p2);
}
程序经过编译后,提示错误为
error:increment of read-only variable ‘p2’
1>const 使用的基本形式为:const char m;
限定m 不可变
2>替换1式中的m,const char *pm;
限定*pm不可变,当然pm是可变的,因此p1++是对的。
3>替换1式中的char,const newType m;
限定m不可变,问题中的pStr是一种新类型,因此问题中p2不可变,p2++是错误的。
(3)const 和指针
类型声明中const用来修饰一个常量,有如下两种写法:
1>const在前面
const int nValue;//nValue是const
const char *pContent;//*pContent是const,pConst可变
const (char *)pContent;//pContent是const,*pContent可变
char *const pContent;//pContent是const,*pContent可变
const char * const pContent;//pContent和*pContent都是const
2>const 在后面与上面的声明对等
int const nValue; // nValue是const
char const * pContent;//*pContent是const, pContent可变
(char *) constpContent;//pContent是const, *pContent可变
char* const pContent;// pContent是const, *pContent可变
char const* const pContent;//pContent和*pContent都是const
说明:const和指针一起使用是C语言中一个很常见的困惑之处,下面是两天规则:
(1)沿着*号划一条线,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。
(2)对于const (char *) ; 因为char *是一个整体,相当于一个类型(如char),因此,这是限定指针是const。