Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1335884
  • 博文数量: 177
  • 博客积分: 3640
  • 博客等级: 中校
  • 技术积分: 1778
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-27 16:51
文章分类

全部博文(177)

文章存档

2014年(1)

2013年(10)

2012年(3)

2011年(163)

分类: C/C++

2011-05-27 18:40:38

C语言变量的存储类别

    内存中供用户使用的存储空间分为代码区与数据区两个部分。变量存储在数据区,数据区又可分为静态存储区与动态存储区。

    静态存储是指在程序运行期间给变量分配固定存储空间的方式。如全局变量存放在静态存储区中,程序运行时分配空间,程序运行完释放。

    动态存储是指在程序运行时根据实际需要动态分配存储空间的方式。如形式参数存放在动态存储区中,在函数调用时分配空间,调用完成释放。

    对于静态存储方式的变量可在编译时初始化,默认初值为O或空字符。对动态存储方式的变量如不赋初值,则它的值是一个不确定的值。

    在C语言中,具体的存储类别有自动(auto)、寄存器(register)、静态(static)及外部(extern)四种。静态存储类别与外部存储类别变量存放在静态存储区,自动存储类别变量存放在动态存储区,寄存器存储类别直接送寄存器。

变量存储类别定义方法:

存储类别类型变量表;

例如:

(1)a,b,c为整型自动存储类别变量:

auto int a,b,c;

(2)x,y,z为双精度型静态存储类别变量:  

static double x,y,z;

1、变量有哪些存储类型?

变量的存储类型由“存储类型指明符”来说明。存储类型指明符可以是下列类键字之一:

auto

register

extern

static

下面是详细的解释:

auto 存储类指明符--用于说明具有局部作用域的变量,它表示变量具有局部(自动)生成期,但由于它是所有局部作用域变量说明的缺省存储类指明符,所以使用得很少。要注意的是,所有在函数内部定义的变量都是局部变量,函数内部定义的变量其作用域只在函数内部。它的生存期为该函数运行期间,一旦离开这个函数或这个函数终止,局部变量也随之消失。

register 存储类指明符--当声明了这个指明符后,编译程序将尽可能地为该变量分配CPU内部的寄存器作为变量的存储单元,以加快运行速度。注意,寄存器与存储器是不同的。寄存器一般在CPU内部,而存储器一般指外部的(比如内存条),CPU内部的寄存器其运算速度是很高的。当寄存器已分配完毕,就自动地分配一个外部的内存。它的作用等价于auto,也只能用于局部变量和函数的参量说明。

static 存储类指明符--表示变量具有静态生成期。static变量的的特点是它离开了其作用域后,其值不会消失。

当回到该作用域之后又可以继续使用这个static变量的值。

例:利用static变量统计调用函数的次数

int two(); /*函数原型说明*/

void main()

{

int a=0;

a=two(); /*a的值等于1*/

a=two() /*a的值等于2*/

a=two(); /*a的值等于3*/

}

int two()

{

static int b=0;    /*定义了一个局部的static变量*/

b ;

return b;

}

如果不是一个static变量就不会有这个效果了

int two(); /*函数原型说明*/

void main()

{

int a=0;

a=two(); /*a的值等于1*/

a=two() /*a的值等于1*/

a=two(); /*a的值等于1*/

}

int two()

{

int b=0;    

b ;

return b;

}

变量a的值总是1,原因是在函数two()中,变量b不是一个static变量,其值随着离开two函数就消失了,当回到two函数时又被重新赋值0。

extern 存储类指明符--一般用在工程文件中。在一个工程文件中因为有多个程序文件,当某一个变量在一个程序文件中定义了之后,如果在另一个程序文件中予以定义,就会出现重复定义变量的错误。使用extern存储类型指明符就可以指出在该文件外部已经定义了这个变量。extern变量的作用域是整个程序。

2、变量存储在内存的什么地方?

   1)变量可以存储在内存的不同地方,这依赖于它们的生成期。在函数上部定义的变量(全局变量或static外部变量)和在函数内部定义的static变量,其生存期就是程序运行的全过程。这些变量被存储在数据段(Data Segment)中。数据段是在内存中为这些变量留出的一段大小固定的空间,它分为二部分,一部分用来初始化变量,另一部分用来存放未初始化的变量。

   2)在函数内部定义的auto变量(没有用关键字static定义的变量)的生成期从程序开始执行其所在的程序块代码时开始,到程序离开该程序块时为止。作为函数参数的变量只在调用该函数期间存在。这些变量被存储在栈(stack)中。栈是内存中的一段空间,开始很小,以后逐渐自动变大,直到达到某个预定义的界限。

   3)当用malloc等函数给指针分配一个地址空间的时候,这个分配的内存块位于一段名为“堆(heap)”的内存空间中。堆开始时很小,但调用malloc或clloc等内存分配函数时它就会增大。堆可以和数据段或栈共用一个内存段,也可以有它自己的内存段,这完全取决于编译选项和操作系统。与栈相似,堆也有一个增长界限,并且决定这个界限的规则与栈相同。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

C语言变量的作用域和存储类型

一、作用域和生存期

      C程序的标识符作用域有三种:局部、全局、文件。标识符的作用域决定了程序中的哪些语句可以使用它,换句话说,就是标识符在程序其他部分的可见性。通常,标识符的作用域都是通过它在程序中的位置隐式说明的。

      1.局部作用域

          前面各个例子中的变量都是局部作用域,他们都是声明在函数内部,无法被其他函数的代码所访问。函数的形式参数的作用域也是局部的,它们的作用范围仅限于函数内部所用的语句块。

          void add(int);

          main()

          {

              int num=5;

              add(num);

              printf("%d\n",num);       /*输出5*/

          }

          void add(int num)

          {

              num++;

              printf("%d\n",num);       /*输出6*/

          }

       

          上面例子里的两个num变量都是局部变量,只在本身函数里可见。前面我们说了,在两个函数出现同名的变量不会互相干扰,就是这个道理。所以上面的两个输出,在主函数里仍然是5,在add()函数里输出是6。

      2.全局作用域

          对于具有全局作用域的变量,我们可以在程序的任何位置访问它们。当一个变量是在所有函数的外部声明,也就是在程序的开头声明,那么这个变量就是全局变量。

          void add(int);

          int num;

          main()

          {

              int n=5;

              add(n);

              printf("%d\n",num);      /*输出6*/

          }

          void add(num)          /*形式参数没有指定类型*/

          {

              num++;

              printf("%d\n",num);      /*输出6*/

          }

      上面的main()和add()里面,并没有声明num,但是在最后输出的时候却要求输出num,这是由于在程序的开始声明了num是全局变量,也就是在所有函数里都可以使用这个变量。这时候一个函数里改变了变量的值,其他函数里的值也会出现影响。上面的例子输出都是6,因为在add()函数里改变了num的值,由于num是全局变量,就好象它们两个函数共用一个变量,所以在main()函数里的num也随之改变了。

      3.文件作用域

          在很多C语言书上,都没有说明文件作用域,或者只是略微的提到,其实文件作用域在较大程序中很有作用(在多文件系统中)。文件作用域是指外部标识符仅在声明它的同一个转换单元内的函数汇总可见。所谓转换单元是指定义这些变量和函数的源代码文件(包括任何通过#i nclude指令包含的源代码文件)。static存储类型修饰符指定了变量具有文件作用域。

          static int num;

          static void add(int);

          main()

          {

              scanf("%d",&num);

              add(num)

              printf("%d\n",num);

          }

          void add(num)

          {

              num++;

          }

      

     上面的程序中变量num和函数add()在声明是采用了static存储类型修饰符,这使得它们具有文件作用域,仅爱定义它们的文件内可见。

     由于我们提到的大多数程序都只有一个编译文件组成,所以这种写法没有实际意义。但是实际工程上的文件有很多,它们不是由一个人写成的,由很多人共同完成,这些文件都是各自编译的,这难免使得某些人使用了一样的全局变量名,那么为了以后程序中各自的变量和函数不互相干扰,就可以使用static修饰符,这样在连接到同一个程序的其他代码文件而言就是不可见的。

二、变量存储类型

      前面我们说了,声明变量时用如下类似的形式:

          int num;

          float total;

       

      它们都没有存储类型修饰符,我们在声明时也可以通过存储类型修饰符来告诉编译器将要处理什么类型的变量。存储类型有以下四种:自动(auto)、静态(static)、外部(extern)、寄存器(regiser)。

      1.自动存储类型

          自动存储类型修饰符指定了一个局部变量为自动的,这意味着,每次执行到定义该变量的语句块时,都将会为该变量在内存中产生一个新的拷贝,并对其进行初始化。实际上,如果不特别指明,局部变量的存储类型就默认为自动的,因此,加不加auto都可以。

              main()

              {

                  auto int num=5;

                  printf("%d\n",num);

              }

          在这个例子中,不论变量num的声明是否包含关键字auto,代码的执行效果都是一样的。函数的形式参数存储类型默认也是自动的。

       

      2.静态存储变量

          前面已经使用了static关键字,但是对于局部变量,静态存储类型的意义是不一样的,这时,它是和自动存储类型相对而言的。静态局部变量的作用域仍然近局限于声明它的语句块中,但是在语句块执行期间,变量将始终保持它的值。而且,初始化值只在语句块第一次执行是起作用。在随后的运行过程中,变量将保持语句块上一次执行时的值。看下面两个对应的程序:

             /*1.C*/                                /*2.C*/

            int add();                             int add();

            main()                                 main()

            {                                      {

                int result;                            int result;

                result=add()                           result=add();

                printf("%d ",result);                  printf("%d ",result);

                result=add();                          result=add();

                printf("%d ",result);                  printf("%d ",result);

                result=add();                          result=add();

                printf("%d",result);                   printf("%d",result);

            }                                      }

            int add()                              int add()

            {                                      {

                int num=50;                            static int num=50;

                num++;                                 num++;

                return num;                            return num;

            }                                      }

      上面两个源文件,只有函数add()里的变量声明有所不同,一个是自动存储类型,一个是静态存储类型。

      对于1.C文件,输出结果为51 51 51;这很好理解,每次初始值都是50,然后加1上来。

      对于2.C文件,输出结果为51 52 53;这是由于变量是静态的,只在第一次初始化了50,以后都是使用上次的结果值。当第一次调用add()时,初始化为50,然后加1,输出为51;当第二次调用时,就不初始化了,这时num的值为上次的51,然后加1,输出52;当第三次调用时,num为52,加1就是53了。

      比较就会发现它们的不同之处了。静态变量在下一节要说的递归函数中经常使用到。

      当第一次不指明静态变量的初始值时,默认为0。

      下面举一个例子,把我们说到的静态变量理解一下。

      求1+2+……+100的值

           void add();

           int result;

           main()

           {

               int i;

               result=0;

               for(i=0;i<100;i++) add();

               printf("%d\n",result);

           }

           void add()

           {

               static int num=0;

               num++;

               result+=num;

           }

          add()函数被调用了100次,num的值从1一直变到100,这样就可以求出它们的和了。如果写成int num=0;那就是求1+1+……+1这100个1的值了。

          实际上类似的这类问题我们可以通过递归函数来解决,什么是递归,我们下一节介绍。

      3.外部存储类型

          外部存储类型声明了程序将要用到的、但尚未定义的外部变量。通常,外部存储类型都是用于声明在另一个转换单元中定义的变量。下面举一个例子,这个例子包括两个文件。

               /*1.C*/

              void a();

              main()

              {

                  extern int num;

                  a();

                  printf("%d\n",num);

              }

               /*2.C*/

              int num;

              void a()

              {

                  num=5;

              }

       

          这两个程序是分别编译的,然后连接成一个执行文件。具体如何操作,可以查看一些手册,这儿我简单说了一下。把上面两个文件都编译好后,再制作一个.prj文件,里面的内容是:

              1.c

              2.c

          只有这两行,这可在编辑状态下写成,存盘,取名为1.prj。

          然后选择project选项,选择project name,填入1.prj文件名,按F9后,即可生成1.exe文件。

       

          main()函数中变量num是在另一个文件中定义的。因此,当编译器编译1.c时,无法确定该变量的地址。这时,外部存储类型声明告诉编译器,把所有对num的引用当作暂且无法确定的引用,等到所有便宜好的目标代码连接成一个可执行程序模块时,再来处理对变量num的引用。

          外部变量的声明既可以在引用它的函数的内部,也可以在外部。如果变量声明在函数外部,那么同一转换单元内的所有函数都可以使用这个外部变量。反之,如果在函数内部,那么只有这一个函数可以使用该变量。

       

          前面说了文件作用域的问题,如果在声明全局变量时,加上static修饰符,那么该变量只在当前文件内可见,而extern又可以引用其它文件里的变量。所以在一个大型程序中,每个程序员只是完成其中的一小块,为了让自己的变量不让其他程序员使用,保持一定的独立性,经常在全局变量前加static。我们可以这样来说明一下:

          还是上面的两个文件,现在再增加一个文件3.c,内容为:

              static int num;

              void a()

              {

                  num=6;

              }

          把1.prj文件后面加上3.c 这样,我们生成的1.exe文件,执行时输出是5,而不是6。因为3.c文件的num变量增加了文件作用域,在其他文件中是无法使用它的。

      4.寄存器存储类型

          被声明为寄存器存储类型的变量,除了程序无法得到其地址外,其余都和自动变量一样。至于什么是变量地址,以后说指针时会详细介绍。

              main()

              {

                  register int num;

                  num=100;

                  printf("%d",num);

              }

      

      使用寄存器存储类型的目的是让程序员指定某个局部变量存放在计算机的某个硬件寄存器里而不是内存中,以提高程序的运行速度。不过,这只是反映了程序员的主观意愿,编译器可以忽略寄存器存储类型修饰符。

      寄存器变量的地址是无法取得的,因为绝大多数计算机的硬件寄存器都不占用内存地址。而且,即使编译器忽略寄存器类型修饰符把变量放在可设定地址的内存中,我们也无法取地址的限制仍然存在。

      要想有效的利用寄存器存储类型,必须象汇编语言程序员那样了解处理器的内部构造,知道可用于存放变量的寄存器的数量和种类,以及他们是如何工作的。但是,不同计算机在这些细节上未必是一样的,因此对于一个可移植的程序来说,寄存器存储类型的作用不大。特别是现在很多编译器都能提供很好的优化效果,远比程序员来选择有效的多。不过,寄存器存储类型还是可以为优化器提供重要的参考。

C的作用域还有一种,静态块。比如:

/* 静态块作用域 */

{

...;

...;

}

/* 函数作用域 */

main()

{

...;

}

转自:http://www.mcublog.com/blog/user1/9450/archives/2007/26248.html

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

由编译器自动分配释放管理。局部变量及每次函数调用时返回地址、以及调用者的环境信息(例如某些机器寄存器)都存放在栈中。新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,C函数可以递归调用。

需要由程序员分配释放管理,若程序员不释放,程序结束时可能由OS回收。通常在堆中进行动态存储分配。

非初始化数据段

通常将此段称为b s s段,这一名称来源于早期汇编程序的一个操作符,意思是“block started by symbol(由符号开始的块)”,未初始化的全局变量和静态变量存放在这里。在程序开始执行之前,内核将此段初始化为0。函数外的说明:long sum[1000] ; 使此变量存放在非初始化数据段中。

初始化的数据

通常将此段称为数据段,它包含了程序中需赋初值的变量。初始化的全局变量和静态变量存放在这里。例如,C程序中任何函数之外的说明:int maxcount = 99; 使此变量以初值存放在初始化数据段中。

正文段

C P U执行的机器指令部分。通常,正文段是可共享的,所以即使是经常环境指针环境表环境字符串执行的程序(如文本编辑程序、C编译程序、s h e l l等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外事故而修改其自身的指令。

对于x86处理器上的Linux,正文段从0x08048000单元开始,栈底在0xC0000000之下开始(栈由高地址向低地址方向增长)。堆顶和栈底之间未用的虚拟空间很大。

Shell的size命令可以看到一个程序的正文段(text)、数据段(data)、非初始化数据段(bss)及文件长度.

[foxman@17:01:49 ]$size mydesign

   text    data     bss     dec     hex filename

79210    1380     404   80994   13c62 mydesign

堆与栈的区别由以下几点:

    管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

    空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M。当然,可以修改:   

    打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。

注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。

生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

    从这里可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。

    虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。

无论是堆还是栈,都要防止越界现象的发生,因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果.

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明

转自:http://feizf.blogbus.com/logs/5229522.html

阅读(1203) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~