变量的存储类型
变量的存储类型有以下四种: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
变量的作用域
变量的作用域是指一个范围,是从代码空间的角度考虑问题,它决定了变量的可见性,说明变量在程序的哪个区域可用,即程序中哪些行代码可以使用变量。
作用域有三种:局部作用域、全局作用域、文件作用域,相对应于局部变量(local variable)、全局变量和静态变量(global variable)。
局部变量
大部分变量具有局部作用域,它们声明在函数(包括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); /*可能乱码*/ } --------------------------------------------------------------
全局变量及extern关键字
以下是MSDN对C/C++中extern关键字的解释:
The extern Storage-Class Specifier(C)
A variable declared with the extern storage-class specifier is a reference to a variable with the same name defined at the external level in any of the source files of the program. The internal extern declaration is used to make the external-level variable definition visible within the block. Unless otherwise declared at the external level, a variable declared with the extern keyword is visible only in the block in which it is declared.
The extern Storage-Class Specifier(C++)
The extern keyword declares a variable or function and specifies that it has external linkage (its name is visible from files other than the one in which it's defined). When modifying a variable, extern specifies that the variable has static duration (it is allocated when the program begins and deallocated when the program ends).The variable or function may be defined in another source file, or later in the same file. Declarations of variables and functions at 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++的名字修饰机制将不起作用)。
静态变量及static关键字
以下是MSDN对C/C++中 static 关键字的解释:
Static (C++)
The static keyword can be used to declare variables, functions, class data members and class functions.
By default, an object or variable that is defined outside all blocks has static duration and external linkage.Static duration means that the object orvariable is allocated when the program starts and is deallocated when the program ends.External linkage means that the name of the variable is visible from outside the file in which the variable is declared. Conversely, internal linkage means that the name is not visible outside the file in which the variable is declared.
The static keyword can be used in the following situations.
(1) When you declare a variable or function at file scope(global and/or namespace scope), the static keyword specifies that the variable or function has internal linkage. When you declare a variable, the variable has static duration and the compiler initializes it to 0 unless you specify another value.
(2) When you declare a variable in a function, the static keyword specifies that the variable retains its state between calls to that function.
(3) When you declare a data member in a class declaration, the static keyword specifies that one copy of the member is shared by all instances of the class. A static data member must be defined at file scope. An integral data member that you declare as const static can have an initializer.
(4) When you declare a member function in a class declaration, the static keyword specifies that the function is shared by all instances of the class. A static member function cannot access an instance member because the function does not have an implicit this pointer. To access an instance member, declare the function with a parameter that is an instance pointer or reference.
(5) You cannot declare the members of a union as static. However, a globally declared anonymous 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修饰的静态成员函数作为类函数,不与任何实例相关,自然无法实现多态了。
参考:
Storage class specifiers - @cppreference
C Storage Classes / Storage classes (C++) - @MSDN
Storage classes /C - Storage Classes
Scope and Lifetime of a variable
C Storage Classes and Storage Class Specifiers