在C语言中,每一个变量都有两个属性:数据类型和存储类型。数据类型即常说的字符型、整型、浮点型;存储类型则指变量在内存中的存储方式,它决定了变量的作用域和生存期。
变量的存储类型有以下四种:auto(自动)、register(寄存器)、extern(外部)和static(静态)。其中auto和register用于声明内部变量,auto变量是存储在栈中的,register变量是存储在寄存器中的。static用于声明局部变量,extern用于声明全局变量。
变量声明的一般形式:<存储类型> <数据类型> <变量名列表>
当声明变量时未指定存储类型,则内部变量的存储类型默认为auto型,外部变量的存储类型默认为extern型。
外部变量有两种声明方式:定义性声明和引用性声明。
定义性声明是为了创建变量,即需为变量分配内存。引用性声明是为了建立变量与内存单元之间的关系,表示要引用的变量已在程序源文件中其他地方进行过定义性声明。定义性声明只能放在函数外部,而引用性声明可放在函数外部,也可放在函数内部。
- extern int b; //引用性声明,也可放在函数fun中
- void fun()
- {
- printf("d%",b); //输出
- }
- extern int b=5; //定义性声明,可以省略关键字extern
2.变量的作用域
变量的作用域是指一个范围,是从代码空间的角度考虑问题,它决定了变量的可见性,说明变量在程序的哪个区域可用,即程序中哪些行代码可以使用变量。作用域有三种:局部作用域、全局作用域、文件作用域,相对应于局部变量(local variable)、全局变量和静态变量(global variable)。
2.1 局部变量
大部分变量具有局部作用域,它们声明在函数(包括main函数)内部,因此局部变量又称为内部变量。在语句块内部声明的变量仅在该语句块内部有效,也属于局部变量。局部变量的作用域开始于变量被声明的位置,并在标志该函数或块结束的右花括号处结束。函数的形参也具有局部作用域,参考《x86下的C函数调用惯例》。
- #include <iostream>
- using namespace std;
- int main()
- {
- int x = 0;
- {
- int x=1;
- cout << x << endl;
- {
- cout << x << endl;
- int x = 2; // "x = 1" lost its scope here covered by "x = 2"
- cout << x << endl; // x = 2
- // local variable "x = 2" lost its scope here
- }
- cout << x << endl; // x = 1
- // local variable "x = 1" lost its scope here
- }
- cout << x << endl;
- // local variable "x = 0" lost its scope here
- return 0;
- }
local小结:
局部变量只在块范围内有效,“块”包括自定义{}和函数,包括{}内临时(自动)变量,函数形参和函数体内临时(自动)变量。
内存管理经典笔试题之Test()->GetMemory():
- --------------------------------------------------------------
- void GetMemory1(char *p)
- {
- p = (char *)malloc(100); /*形参指针变量_p值改变*/
- }
- void Test1(void)
- {
- char *str = NULL;
- GetMemory1(str); /*实参指针变量str值仍为NULL*/
- strcpy(str, "hello world"); /*非法内存访问导致程序崩溃*/
- printf(str);
- }
- --------------------------------------------------------------
- char *GetMemory2(void)
- {
- char* p = "hello world"; /*指针指向静态常量区(.rodata),不允许修改*/
- return p;
- }
- void Test2(void)
- {
- char *str = NULL;
- str = GetMemory2();
- printf(str); /*允许只读访问*/
- }
- --------------------------------------------------------------
- char *GetMemory3(void)
- {
- char p[] = "hello world"; /*指针p为栈自动变量*/
- return p;
- } /*栈自动变量将在此释放*/
- void Test3(void)
- {
- char *str = NULL;
- str = GetMemory3(); /*str非NULL,但其所指栈区变量可能被清除*/
- printf(str); /*可能乱码*/
- }
- --------------------------------------------------------------
2.2 全局变量及extern关键字
以下是MSDN对C/C++中extern关键字的解释:
The externStorage-Class Specifier(C)
Avariable declared with the extern storage-class specifier is areferenceto a variable with the same namedefined at the externallevel in any of the source files of the program. Theinternal externdeclaration is used to make the external-level variable definitionvisible within the block. Unless otherwisedeclared at the external level, a variable declared with the extern keyword isvisible only in the block in which it is declared.
The externStorage-Class Specifier(C++)
Theextern keyword declares a variable or function and specifies that it hasexternallinkage (its name is visible from files other than the one in whichit's defined). When modifying a variable, extern specifies that the variablehas static duration (it is allocated when the program begins and deallocatedwhen the program ends). The variable or functionmay be defined in another source file, orlaterin the same file. Declarations of variables and functionsat file scope are external by default.
(1)外部变量引用声明
全局变量声明在函数的外部,因此又称外部变量,其作用域一般从变量声明的位置起,在程序源文件结束处结束。全局变量作用范围最广,甚至可以作用于组成该程序的所有源文件。当将多个独立编译的源文件链接成一个程序时,在某个文件中声明的全局变量或函数,在其他相链接的文件中也可以使用它们,但是必须做extern引用性声明。
关键字extern为声明但不定义一个对象提供了一种方法。实际上,它类似于函数声明,承诺了该对象会在其他地方被定义:或者在此文本文件中的其他地方,或者在程序的其他文本文件中。
如果一个函数要被其他文件中函数使用,定义时加extern关键字,在没有加extern和static关键字时,一般有的编译器会默认是extern类型的,因此你在其他文件中可以调用此函数。因此,extern一般主要用来做引用性声明。但是,有些编译器以及在一些大型项目里,使用时一般的会将函数的定义放在源文件中不加extern,而将函数的声明放在头文件中,并且显式的声明成extern类型,需要使用此函数的源文件只要包含此头文件即可。
在使用extern 声明全局变量或函数时,一定要注意:所声明的变量或函数必须在且仅在一个源文件中实现定义。如果你的程序声明了一个外部变量,但却没有在任何源文件中定义它,程序将可以通编译,但无法链接通过:因为extern声明不会引起内存被分配!
在线程存在的情况下,必须做特殊的编码,以便同步各个线程对于全局对象的读和写操作。
(2)C++中的extern “C”链接指定
众所周知,强大而复杂的C++拥有类、继承、虚函数机制、重载、名称空间等特性,它们使得符号管理更为复杂。例如,C++允许定义参数列表不同的同名函数:int func(int)和int fun(double)。那么编译器在链接过程中是怎样区分这两个函数的呢?为了支持C++这些复杂的特性,人们发明了符号修饰(Name Decoration)或符号改变(Name Mangling)的机制区分函数符号实现重载。
C++为了与C兼容,在符号管理上,提供了extern "C"关键字解决名字匹配问题,实现C++与C及其它语言的混合编程。C++编译器会将在extern "C"修饰的大括号内的代码当做C语言代码处理,按照C语言方式进行编译和链接,C++的名字修饰机制将不起作用。
如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。
In C++, when used with a string, externspecifies that the linkage conventions of another language are being used forthe declarator(s). C functions and data can be accessed only if they arepreviously declared as having C linkage. However, they must be defined in aseparately compiled translation unit.
Microsoft C++ supports the strings"C" and "C++" in the string-literal field. All of thestandard include files use the extern "C" syntax to allow therun-time library functions to be used in C++ programs.
例如,在C++工程中要包含C语言头文件,则一般这样:
extern "C" { #include <stdio.h> }
示例工程testExtern包含三个文件:C.c、CPP.cpp和testExtern.cpp。
- // C.c
- #include <stdio.h>
- int intC = 2010;
- void funC()
- {
- printf("funC()\n");
- }
- // CPP.cpp
- #include <stdio.h>
- extern int global;
- /*extern*/ int intCPP = 2011;
- /*extern*/ const char* str = "defined outside";
- /*extern*/ int intArray[3] = {2012, 2013, 2014};
- static int staticIntCPP = 2015;
- void funCPP()
- {
- printf("funCPP() - localStatic : %d, globalExtern : %d\n", staticIntCPP, global);
- }
- // testExtern.cpp
- #include <stdio.h>
- /*extern*/ int global = 2016;
- extern "C" void funC(); // C.c中实现
- /*extern*/ void funCPP(); // CPP.cpp中实现,函数的声明默认在前面添加了extern:因为此处只声明,肯定在其他地方实现的。
- // 以下代码按C方式编译链接
- extern "C" void funC1()
- {
- printf("funC1()\n");
- }
- extern "C"
- {
- void funC2()
- {
- printf("funC2()\n");
- }
- }
- extern "C" void funC3(); // 本文件中其他地方(或外部文件)实现,按照C方式编译链接
- /*extern*/ void fun(); // 本文件中其他地方(或外部文件)实现
- extern "C" int intC; // C linkage specification in C++ must be at global scope
- int main()
- {
- printf("intC = %d\n", intC);
- extern int intCPP; // 或者放在main之前。如果去掉extern就变成了main()内部定义的局部变量!
- printf("intCPP = %d\n", intCPP);
- extern const char* str; // 或者放在main之前。
- printf("str = %s\n", str);
- extern int intArray[];
- for (int i = 0; i < 3; i++)
- {
- printf("intArray[i] = %d\n", intArray[i]);
- }
- // extern int staticIntCPP; // error LNK2001
- // printf("staticIntCPP = %d\n", staticIntCPP);
- funC();
- funCPP();
- funC1();
- funC2();
- funC3();
- fun();
- return 0;
- }
- void funC3()
- {
- printf("funC3()\n");
- }
- void fun()
- {
- printf("fun()\n");
- }
extern小结:
(1)extern支持项目工程内跨源文件级别的调用(引用),只要在链接(包括静态链接和动态)时能够找到符号定义即可。
(2)C++中使用extern “C”指定按照C语言方式进行编译和链接(C++的名字修饰机制将不起作用)。
2.3 静态变量及static关键字
以下是MSDN对C/C++中extern关键字的解释:
Static (C++)
The statickeyword can be used to declare variables, functions, class data members andclass functions.
Bydefault, an object or variable that is defined outside all blocks has staticduration and external linkage.Static durationmeans that the object orvariable isallocated when the program starts andis deallocated when the program ends.External linkage means that thename of the variable is visible from outside the file in which the variable isdeclared. Conversely,internal linkage means that the name is notvisible outside the file in which the variable is declared.
The statickeyword can be used in the following situations.
(1) Whenyou declare a variable or functionat file scope(global and/or namespace scope), the static keyword specifies that the variableor function has internal linkage. When you declare a variable, the variable hasstatic duration and the compilerinitializes it to0 unless you specify another value.
(2) Whenyou declare a variable in a function, the static keyword specifies that thevariableretains its state between calls to thatfunction.
(3) Whenyou declare a data member in a classdeclaration, the static keyword specifies that one copy of the member isshared by all instances of the class. A staticdata member must be defined at file scope. An integral data member that youdeclare as const static can have an initializer.
(4) Whenyou declare a member function in a classdeclaration, the static keyword specifies that the function isshared by all instances of the class. A staticmember function cannot access an instance member because the functiondoes not have an implicit this pointer. To accessan instance member, declare the function with a parameter that is an instancepointer or reference.
(5) Youcannot declare the members of a union as static. However, a globally declaredanonymous union must be explicitly declared static.
文件作用域是指在函数外部声明的变量只在当前文件范围内(包括该文件内所有定义的函数)可用,但不能被其他文件中的函数访问。一般在具有文件作用域的变量或函数的声明前加上static修饰符。
static静态变量可以是全局变量,也可以是局部变量,但都具有全局的生存周期,即生命周期从程序启动到程序结束时才终止。
- #include <stdio.h>
- void fun()
- {
- static int a=5; // 静态变量a是局部变量,但具有全局的生存期
- a++;
- printf("a=%d\n",a);
- }
- int main()
- {
- int i;
- for(i=0;i<2;i++)
- fun();
- getchar();
- return 0;
- }
输出结果为:
a=6
a=7
static操作符后面生命的变量其生命周期是全局的,而且其定义语句即static int a=5;只运行一次,因此之后再调用fun()时,该语句不运行。所以f的值保留上次计算所得,因此是6,7。
以下initWinsock例程中借助局部静态变量_haveInitializedWinsock保证Winsock只初始化一次。
- int initWinsock(void)
- {
- static int _haveInitializedWinsock = 0;
- WORD WinsockVer1 = MAKEWORD(1, 1);
- WORD WinsockVer2 = MAKEWORD(2, 2);
- WSADATA wsadata;
- if (!_haveInitializedWinsock)
- {
- if (WSAStartup(WinsockVer1, &wsadata) && WSAStartup(WinsockVer2, &wsadata))
- {
- return 0; /* error in initialization */
- }
- if ((wsadata.wVersion != WinsockVer1)
- && (wsadata.wVersion != WinsockVer2))
- {
- WSACleanup();
- return 0; /* desired Winsock version was not available */
- }
- _haveInitializedWinsock = 1;
- }
- return 1;
- }
同一个源程序文件中的函数之间是可以互相调用的,不同源程序文件中的函数之间也是可以互相调用的,根据需要我们也可以指定函数不能被其他文件调用。根据函数能否被其他源程序文件调用,将函数分为内部函数和外部函数。
如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static。内部函数又称静态函数。使用内部函数,可以使函数只局限于所在文件,如果在不同的文件中有同名的内部函数,互不干扰。
通常把只能由同一文件使用的函数和外部变量放在一个文件中,在它们前面都冠以static使之局部化,其他文件不能引用。
由于静态变量或静态函数只在当前文件(定义它的文件)中有效,所以我们完全可以在多个文件中,定义两个或多个同名的静态变量或函数。这样当将多个独立编译的源文件链接成一个程序时,static修饰符避免一个文件中的外部变量由于与其他文件中的变量同名而发生冲突。
比如在A文件和B文件中分别定义两个静态变量a:
A文件中:static int a;
B文件中:static int a;
这两个变量完全独立,之间没有任何关系,占用各自的内存地址。你在A文件中改a的值,不会影响B文件中那个a的值。
static小结:
(1)首先static的功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。初始化了的全局变量和局部静态变量存放在.data段;未初始化的全局变量和局部静态变量存放在.bss段。
(2)C++类的成员函数不可同时为virtual和static,因为多态实现的基本原理是每个带有virtual函数的类的实例要包含一个指针,指向虚函数表(vtbl)。static修饰的静态成员函数作为类函数,不与任何实例相关,自然无法实现多态了。
参考:
《程序设计与C语言》 梁力
《白话C++》 南郁
《Visual C++面向对象编程教程》 王育坚
《C++ Primer》Stanley B. Lippman
《程序员的自我修养》