Chinaunix首页 | 论坛 | 博客
  • 博客访问: 337915
  • 博文数量: 82
  • 博客积分: 2602
  • 博客等级: 少校
  • 技术积分: 660
  • 用 户 组: 普通用户
  • 注册时间: 2007-08-10 08:48
文章分类

全部博文(82)

文章存档

2008年(17)

2007年(65)

分类: C/C++

2007-09-03 16:24:47

1 概述
C语言的强大功能之一是可以灵活地定义数据的存储方式。
C语言从两个方面控制变量的性质:作用域(scope)和生存期(lifetime)。作用域是指可以存取变量的代码范围,生存期是指可以存取变量的时间范围。
作用域有三种:
    1. extern(外部的)  这是在函数外部定义的变量的缺省存储方式。extern变量的作用域是整个程序
    2.static(静态的)  在函数外部说明为static的变量的作用域为从定义点到该文件尾部;在函数内部说明为static的变量的作用域为从定义点到该局部程序块尾部
    3.auto(自动的)  这是在函数内部说明的变量的缺省存储方式。auto变量的作用域为从定义点到该局部程序块尾部。
    变量的生存期也有三种,但它们不象作用域那样有预定义的关键字名称。
第一种是extern和static变量的生存期,它从main()函数被调用之前开始,到程序退出时为止。
第二种是函数参数和auto变量的生存期,它从函数调用时开始,到函数返回时为止。
第三种是动态分配的数据的生存期,它从程序调用malloc()或calloc()为数据分配存储空间时开始,到程序调用free()或程序退出时为止。
2 变量在内存中存储位置
变量可以存储在内存中的不同地方,这依赖于它们的生存期
   在函数外部定义的变量(全局变量或静态外部变量)和在函数内部定义的static变量,其生存期就是程序运行的全过程,这些变量被存储在数据段(datasegment)中。数据段是在内存中为这些变量留出的一段大小固定的空间,它分为两部分,一部分用来存放初始化变量,另一部分用来存放未初始化变量。
   在函数内部定义的auto变量(没有用关键字static定义的变量)的生存期从程序开始执行其所在的程序块代码时开始,到程序离开该程序块时为止。作为函数参数的变量只在调用该函数期间存在。这些变量被存储在栈(stack)中。栈是内存中的一段空间,开始很小,以后逐渐自动增大,直到达到某个预定义的界限。在象DOS这样的没有虚拟内存(virtual memory)的系统中,这个界限由系统决定,并且通常非常大,因此程序员不必担心用尽栈空间。    
   第三种(也是最后一种)内存空间实际上并不存储变量,但是可以用来存储变量所指向的数据。如果把调用malloc()函数的结果赋给一个指针变量,那么这个指针变量将包含一块动态分配的内存的地址,这块内存位于一段名为“堆(heap)”的内存空间中。堆开始时也很小,但当程序员调用malloc()或calloc()等内存分配函数时它就会增大。堆可以和数据段或栈共用一个内存段(memorysegment),也可以有它自己的内存段,这完全取决于编译选项和操作系统。与栈相似,堆也有一个增长界限,并且决定这个界限的规则与栈相同。
3 变量是否一定要初始化
不。使用变量之前应该给变量一个值,一个好的编译程序将帮助你发现那些还没有被给定一个值就被使用的变量。不过,变量不一定需要初始化。在函数外部定义的变量或者在函数内部用static关键字定义的变量(被定义在数据段中的那些变量)在没有明确地被程序初始化之前都已被系统初始化为0了在函数内部或程序块内部定义的不带static关键字的变量都是自动变量,如果你没有明确地初始化这些变量,它们就会具有未定义值。如果你没有初始化一个自动变量,在使用它之前你就必须保证先给它赋值。
    调用malloc()函数从堆中分配到的空间也包含未定义的数据,因此在使用它之前必须先进行初始化(char *p=NULL;p=(char *)malloc(1024););但调用calloc()函数分配到的空间在分配时就已经被初始化为0了。
4 const指针
 如果希望一个变量在被初始化后其值不会被修改,程序员就会通过const修饰符和编译程序达成默契。编译程序会努力去保证这种默契——它将禁止程序中出现对说明为const的变量进行修改的代码
    const指针的准确提法应该是指向const数据的指针,即它所指向的数据不能被修改。只要在指针说明的开头加入const修饰符,就可说明一个cosnt指针。尽管const指针所指向的数据不能被修改,但cosnt指针本身是可以修改的。下面给出了const指针的一些合法和非法的用法例子:
    const char *str="hello";
    char c=*str;  /*legal*/
    str++;       /*legal*/
    *str='a';     /* illegal */
    str[1]='b';  /*illegal*/
    前两条语句是合法的,因为它们没有修改str所指向的数据;后两条语句是非法的,因为它们要修改str所指向的数据。
    在说明函数参数时,常常要使用const指针。例如,一个计算字符串长度的函数不必改变字符串内容,它可以写成这样:
    my_strlen(const char *str)
    {
        int count=0;
        while ( * str++)
        {
            count ++;
        }
        return   count;
    }

   注意,如果有必要,一个非const指针可以被隐式地转换为const指针,但一个const指针不能被转换成非const指针。这就是说,在调用my_strlen()时,它的参数既可以是一个const指针,也可以是一个非const指针。
5 register修饰符
register修饰符暗示编译程序相应的变量将被频繁使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存取速度。但是,使用register修饰符有几点限制。
   首先,register变量必须是能被CPU寄存器所接受的类型。这通常意味着register变量必须是一个单个的值,并且其长度应小于或等于整型的长度。但是,有些机器的寄存器也能存放浮点数。
   其次,因为register变量可能不存放在内存中,所以不能用取址运算符“&”来获取register变量的地址。如果你试图这样做,编译程序就会报告这是一个错误。
   register修饰符的用处有多大还受其它一些规则的影响。因为寄存器的数量是有限的,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此,真正能起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。
   在某些情况下,把变量保存在寄存器中反而会降低运行速度,因为被占用的寄存器不能再用于其它目的,或者变量被使用的次数不够多,不足以抵消装入和存储变量所带来的额外开销。
   那么,什么时候应该使用register修饰符呢?回答是,对现有的大多数编译程序来说,永远不要使用register修饰符。早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定哪些变量应该被存到寄存器中时,现在的C编译程序能比程序员作出更好的决定。
  实际上,许多C编译程序会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令
  在极罕见的情况下,程序运行速度很慢,而你也知道这是因为有一个变量被存储在内存中,也许你最后会试图在该变量前面加上register修饰符,但是,如果这并没有加快程序的运行速度,你也不要感到奇怪。
6 valatile修饰符(暂时不用掌握)
volatile修饰符告诉编译程序不要对该变量所参与的操作进行某些优化
在两种特殊的情况下需要使用volatile修饰符:
第一种情况涉及到内存映射硬件(memory-mapped hardware,如图形适配器,这类设备对计算机来说就好象是内存的一部分一样)
第二种情况涉及到共享内存(shared memory,即被两个以上同时运行的程序所使用的内存)。
    大多数计算机拥有一系列寄存器,其存取速度比计算机主存更快。好的编译程序能进行一种被称为“冗余装入和存储的删去”(redundant load and store removal)的优化,即编译程序会在程序中寻找并删去这样两类代码:一类是可以删去的从内存装入数据的指令,因为相应的数据已经被存放在寄存器中;另一种是可以删去的将数据存入内存的指令,因为相应的数据在再次被改变之前可以一直保留在寄存器中。
    如果一个指针变量指向普通内存以外的位置,如指向一个外围设备的内存映射端口,那么冗余装入和存储的优化对它来说可能是有害的。例如,为了调整某个操作的时间,可能会用到下述函数:   

time_t time_addition(volatile const struct timer * t, int a)
{
         int    n;
         int    x;
         time_t  then;
         x=O;
         then= t->value;
         for (n=O; n<1O00; n++)
         {
               x=x+a ;
         }
      return t->value - then;
}

    在上述函数中,变量t->value实际上是一个硬件计数器,其值随时间增加。该函数执行1000次把a值加到x上的操作,然后返回t->value在这1000次加法的执行期间所增加的值。
    如果不使用volatile修饰符,一个聪明的编译程序可能就会认为t->value在该函数执行期间不会改变,因为该函数内没有明确地改变t->value的语句。这样,编译程序就会认为没有必要再次从内存中读入t->value并将其减去then,因为答案永远是0。因此,编译程序可能会对该函数进行“优化”,结果使得该函数的返回值永远是0。
    如果一个指针变量指向共享内存中的数据,那么冗余装入和存储的优化对它来说可能也是有害的,共享内存通常用来实现两个程序之间的互相通讯,即让一个程序把数据存到共享的那块内存中,而让另一个程序从这块内存中读数据。如果从共享内存取得数据或把数据存入共享内存的代码被编译程序优化掉了,程序之间的通讯就会受到影响。   

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