c语言中的全局变量,局部变量,static,extern

变量可以在程序中三个地方说明: 函数内部、函数的参数定义中或所有的函数外部。根据所定义位置的不同, 变量可分为局部变量、形式参数和全程变量。从空间角度来看,变量可以分为全局变量和局部变量,而从时间角度来分的 可以有静态存储变量和动态存储变量之分。

 

一。全局变量和局部变量

1。局部变量

他是 指在函数内部定义的变量 作用域为定义局部变量的函数 也就是说他只能在定义他的函数中使用

最值得注意的是 只有在程序执行到定义他的模块时才能生成,一旦执行退出该模块则起变量消失

2。全局变量

在程序执行的过程中一直有效

 

对于全局变量 如果在定义的时候不做初始化 则系统将自动为起赋值 数值型为0,字符型为空'\0'

全局变量的弊端 增加内存开销 降低函数的通用性

 

定义全局变量时 理想的位置是在文件的开头 当这些函数以及同一个程序中的其他源程序文件中的某些函数需要使用该全局变量时 在函数内部对该变量使用extern 加以说明 说明他是外部的。

 

还要说明的是 对外部变量的说明和对全局变量的定义不是一回事

对外部变量的说明 只是声明该变量是在外部定义过的一个全局变量 在这里引用 而对全局变量的定义则是要对起分配存储单元的

一个全局变量只能定义一次 可是却可以多次引用

*** 在同一源文件中,全局变量和局部变量同名时,在局部变量的作用域内,全局变量不起作用的。

 

二。静态存储变量和动态存储变量

对于程序运行期间根据需要进行临时动态分配存储空间的变量 为动态存储变量

对于那些程序运行期间永久占用固定内存的变量 称为静态存储变量


还要说明的是 程序的指令代码是存放在程序代码区的 静态存储变量是存放在静态数据区的 包括全局变量等 而程序中的动态存储变量存放在动态数据区 如函数的形参以及函数调用时的返回地址等

 

三。C语言中的变量存储分类指定
    
     auto 
     auto称为自动变量 如果函数不做其他说明的话 均为自动变量


     static 
     static称为静态变量。根据变量的类型可以分为静态局部变量和静态全程变量。 
     1. 静态局部变量

      它与局部变量的区别在于: 在函数退出时, 这个变量始终存在, 但不能被其它函数使用, 当再次进入该函数时, 将保存上次的结果。其它与局部变量一样。

     2. 静态全程变量

      Turbo C2.0允许将大型程序分成若干独立模块文件分别编译, 然后将所有模块的目标文件连接在一起, 从而提高编译速度, 同时也便于软件的管理和维护。静态全程变量就是指只在定义它的源文件中可见而在其它源文件中不可见的变量。它与全程变量的区别是: 全程变量可以再说明为外部变量(extern), 被其它源文件使用, 而静态全程变量却不能再被说明为外部的, 即只能被所在的源文件使用。 

     extern 
     extern称为外部变量。为了使变量除了在定义它的源文件中可以使用外, 还要被其它文件使用。因此,    必须将全程变量通知每一个程序模块文件,    此时可用extern来说明。

 

     register 
     register称为寄存器变量。它只能用于整型和字符型变量。定义符register说明的变量被Turbo C2.0存储在CPU的寄存器中,   而不是象普通的变量那样存储在内存中, 这样可以提高运算速度。但是Turbo C2.0只允许同时定义两个寄存器变量,一旦超过两个, 编译程序会自动地将超过限制数目的寄存器变量当作非寄存器变量来处理。因此, 寄存器变量常用在同一变量名频繁出现的地方。 
     另外, 寄存器变量只适用于局部变量和函数的形式参数, 它属于auto型变量, 因此, 不能用作全程变量。定义一个整型寄存器变量可写成: 
       register int a;


static关键字是C,C++中都存在的关键字, 它主要有三种使用方式, 其中前两种只指在C语言中使用, 第三种在C++中使用(C,C++中具体细微操作不尽相同, 本文以C++为准).

(1)局部静态变量

(2)外部静态变量/函数

(3)静态数据成员/成员函数

下面就这三种使用方式及注意事项分别说明

 

一、局部静态变量

在C/C++中, 局部变量按照存储形式可分为三种auto, static, register

(谭浩强,第174-175页)

与auto类型(普通)局部变量相比, static局部变量有三点不同

1. 存储空间分配不同

auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.

2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次

3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符, 而auto类型的初值是不确定的. (对于C++中的class对象例外,class的对象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类型)

 

特点: static局部变量的”记忆性”与生存期的”全局性”

所谓”记忆性”是指在两次函数调用时, 在第二次调用进入时, 能保持第一次调用退出时的值.

示例程序一

#include

 

using namespace std;

 

void staticLocalVar()

{

 static int a = 0; // 运行期时初始化一次, 下次再调用时, 不进行初始化工作

 cout<<"a="<

 ++a;

}

 

int main()

{

 staticLocalVar(); // 第一次调用,输出a=0

 staticLocalVar(); // 第二次调用,记忆了第一次退出时的值, 输出a=1

 return 0;

}

 

应用:

 利用”记忆性”, 记录函数调用的次数(示例程序一)

   利用生存期的”全局性”, 改善”returna pointer / reference to a local object”的问题. Local object的问题在于退出函数, 生存期即结束,. 利用static的作用,延长变量的生存期.

示例程序二:

// IP address to stringformat

// Used in EthernetFrame and IP Header analysis

const char *IpToStr(UINT32 IpAddr)

{

 static char strBuff[16]; // static局部变量,用于返回地址有效

 const unsigned char *pChIP = (const unsignedchar *)&IpAddr;

 sprintf(strBuff, "%u.%u.%u.%u",  pChIP[0], pChIP[1], pChIP[2], pChIP[3]);

 return strBuff;

}

 

void fun(int i)

 

{

  static int value=i++;

  cout << value << endl;

}

int main()

 

{

  fun(0);

  fun(1);

  fun(2);

 

  return 0;

}

 

输出 000

 

 

你知道是为什么!

 

 

注意事项:

1. “记忆性”,程序运行很重要的一点就是可重复性, 而static变量的”记忆性”破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.

2. “生存期”全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具有全局唯一性的特点, 每次调用时,都指向同一块内存, 这就造成一个很重要的问题 ---- 不可重入性!!!

这样在多线程程序设计或递归程序设计中, 要特别注意这个问题.

(不可重入性的例子可以参见(影印版)第103-105页)

下面针对示例程序二, 分析在多线程情况下的不安全性.(为方便描述, 标上行号)

① const char * IpToStr(UINT32 IpAddr)

② {

③  static charstrBuff[16]; // static局部变量, 用于返回地址有效

④  const unsigned char*pChIP = (const unsigned char *)&IpAddr;

⑤  sprintf(strBuff,"%u.%u.%u.%u",  pChIP[0],pChIP[1], pChIP[2], pChIP[3]);

⑥  return strBuff;

⑦ }

假设现在有两个线程A,B运行期间都需要调用IpToStr()函数, 将32位的IP地址转换成点分10进制的字符串形式. 现A先获得执行机会, 执行IpToStr(), 传入的参数是0x0B090A0A,顺序执行完应该返回的指针存储区内容是:”10.10.9.11”, 现执行到⑥时, 失去执行权, 调度到B线程执行, B线程传入的参数是0xA8A8A8C0, 执行至⑦, 静态存储区的内容是192.168.168.168. 当再调度到A执行时,从⑥继续执行, 由于strBuff的全局唯一性, 内容已经被B线程冲掉,此时返回的将是192.168.168.168字符串, 不再是10.10.9.11字符串.

 

二、外部静态变量/函数

在C中static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。, 但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.

使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

示例程序三:

 

//file1.cpp

 

static int varA;

int varB;

extern void funA()

{

……

}

 

static void funB()

{

……

}

 

//file2.cpp

 

extern int varB; // 使用file1.cpp中定义的全局变量

extern int varA; // 错误!varA是static类型, 无法在其他文件中使用

extern vod funA(); // 使用file1.cpp中定义的函数

extern void funB(); // 错误! 无法使用file1.cpp文件中static函数

三、静态数据成员/成员函数(C++特有)

C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数. 这是与普通成员函数的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时, 计数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既不是限定作用域的, 也不是扩展生存期的作用, 而是指示变量/函数在此类中的唯一性. 这也是”属于一个类而不是属于此类的任何特定对象的变量和函数”的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static, 在内存中只有一个副本, 普通成员函数调用时, 需要传入this指针, static成员函数调用时, 没有this指针. )

请看示例程序四((影印版)第59页)

class EnemyTarget {

public:

  EnemyTarget() { ++numTargets; }

  EnemyTarget(const EnemyTarget&) {++numTargets; }

  ~EnemyTarget() { --numTargets; }

  static size_t numberOfTargets() { returnnumTargets; }

  bool destroy();   // returns success of attempt to destroyEnemyTarget object

private:

  static size_t numTargets;               // object counter

};

// class statics mustbe defined outside the class;

// initialization is to0 by default

size_tEnemyTarget::numTargets;

 

在这个例子中, 静态数据成员numTargets就是用来计数产生的对象个数的.

另外, 在设计类的多线程操作时, 由于POSIX库下的线程函数pthread_create()要求是全局的,普通成员函数无法直接做为线程函数, 可以考虑用Static成员函数做线程函数.

 

其实正如前面我引用的一篇文章中说的那样,static有两个作用:

 

1.限制变量和函数的作用域;

 

2.限制变量的存储域。

 

  extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

另外,extern也可用来进行链接指定。

 

  在一个源文件里定义了一个数组:char a[6];

  在另外一个文件里用下列语句进行了声明:extern char *a;

  请问,这样可以吗?

  答案与分析:

  1)、不可以,程序运行时会告诉你非法访问。原因在于,指向类型T的指针并不等价于类型T的数组。extern char *a声明的是一个指针变量而不是字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为extern char a[ ]。

  2)、例子分析如下,如果a[] = "abcd",则外部变量a=0x12345678 (数组的起始地址),而*a是重新定义了一个指针变量a的地址可能是0x87654321,直接使用*a是错误的.

  3)、这提示我们,在使用extern时候要严格对应声明时的格式,在实际编程中,这样的错误屡见不鲜。

  4)、extern用在变量声明中常常有这样一个作用,你在*.c文件中声明了一个全局的变量,这个全局的变量如果要被引用,就放在*.h中并用extern来声明。

 

函数

  extern 函数1

  常常见extern放在函数的前面成为函数声明的一部分,那么,C语言关键字extern在函数的声明中起什么作用?

  答案与分析:

  如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有明显的区别:

  extern int f(); 和int f();

  当然,这样的用处还是有的,就是在程序中取代include “*.h”来声明函数,在一些复杂的项目中,我比较习惯在所有的函数声明前添加extern修饰。

  extern 函数2

  当函数提供方单方面修改函数原型时,如果使用方不知情继续沿用原来的extern申明,这样编译时编译器不会报错。但是在运行过程中,因为少了或者多了输入参数,往往会照成系统错误,这种情况应该如何解决?

  答案与分析:

  目前业界针对这种情况的处理没有一个很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供对外部接口的声明,然后调用方include该头文件,从而省去extern这一步。以避免这种错误。

  宝剑有双锋,对extern的应用,不同的场合应该选择不同的做法。

  extern “C”

  在C++环境下使用C函数的时候,常常会出现编译器无法找到obj模块中的C函数定义,从而导致链接失败的情况,应该如何解决这种情况呢?

  答案与分析:

  C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。

  下面是一个标准的写法:

  //在.h文件的头上

  #ifdef __cplusplus

  #if __cplusplus

  extern "C"{

  #endif

  #endif /* __cplusplus */

  …

  …

  //.h文件结束的地方

  #ifdef __cplusplus

  #if __cplusplus

  }

  #endif

#endif /* __cplusplus */

__cplusplus标示符用来判断程序是用c还是c++编译程序编译的。当编译c++程序时,这个标示符会被定义,编译c程序时,不会定义。


你可能感兴趣的:(c程序设计语言笔记)