(1)auto
这个这个关键字用于声明变量的生存期为自动,即将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。这个关键字不怎么多写,因为所有的变量默认就是auto的。
(2)register
这个关键字命令编译器
尽可能的将变量存在
CPU内部寄存器中而不是通过内存寻址访问以提高效率。
(3)static
常见的两种用途:
1>统计函数被调用的次数;
2>
减少局部数组建立和赋值的开销.变量的建立和赋值是需要一定的处理器开销的,特别是数组等含有较多元素的存储类型。在一些含有较多的变量并且被经常调用的函数中,可以将一些数组声明为static类型,以减少建立或者初始化这些变量的开销.
详细说明:
1>、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
2>、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。
3>当static用来修饰全局变量时,它就改变了全局变量的作用域,使其不能被别的程序extern,限制在了当前文件里,但是没有改变其存放位置,还是在全局静态储存区。
使用注意:
1>若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
2>若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
3>设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题(只要输入数据相同就应产生相同的输出)。
(4)const
被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。它可以修饰函数的参数、返回值,甚至函数的定义体。
作用:
1>修饰输入参数
a.对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。
b.对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。
2>用const修饰函数的返回值
a.如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。
如对于: const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();//cannot convert from 'const char *' to 'char *';
正确的用法是:
const char *str = GetString();
b.如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。 如不要把函数int GetInt(void) 写成const int GetInt(void)。
3>const成员函数的声明中,const关键字只能放在函数声明的尾部,表示该类成员不修改对象.
说明:
const type m; //修饰m为不可改变
示例:
typedef char * pStr; //新的类型pStr;
char string[4] = "abc";
const char *p1 = string;
p1++; //正确,上边修饰的是*p1,p1可变
const pStr p2 = string;
p2++; //错误,上边修饰的是p2,p2不可变,*p2可变
同理,const修饰指针时用此原则判断就不会混淆了。
const int *value; //*value不可变,value可变
int* const value; //value不可变,*value可变
const (int *) value; //(int *)是一种type,value不可变,*value可变
//逻辑上这样理解,编译不能通过,需要tydef int* NewType;
const int* const value;//*value,value都不可变
(5)volatile
表明某个变量的
值可能在外部被改变,
优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。它可以适用于基础类型如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者类的所有成员都会被视为volatile.
该关键字在
多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程。
简单示例:
DWORD __stdcall threadFunc(LPVOID signal)
{
int* intSignal=reinterpret_cast(signal);
*intSignal=2;
while(*intSignal!=1)
sleep(1000);
return 0;
}
该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了:
mov ax,signal
label:
if(ax!=1)
goto label
对于C编译器来说,它并不知道这个值会被其他线程修改。自然就
把它cache在寄存器里面。
C 编译器是没有线程概念的,这时候就需要用到volatile。volatile
的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时
候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示:
label:
mov ax,signal
if(ax!=1)
goto label
注意:一个参数既可以是const同时是volatile,是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
(6)extern
extern 意为“外来的”···它的作用在于告诉编译器:有这个变量,它可能不存在当前的文件中,但它肯定要存在于工程中的某一个源文件中或者一个Dll的输出中。
下面关于C++的几个关键字是经常和我们打交道的而我们又经常对这些含糊不清的,本文根据自己的学习体会作以总结,以期达到真正理解和活用的目的。
static
l 静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,使用时可改变其值。
l 静态变量或静态函数,即只有本文件内的代码才可访问它,它的名字(变量名或函数名)在其它文件中不可见。
l 在函数体内生成的静态变量它的值也只能维持
int max_so_far( int curr )//求至今(本次调用)为止最大值
{
static int biggest; //该变量保持着每次调用时的最新值,它的有效期等于整个程序的有效期
if( curr > biggest )
biggest = curr;
return biggest;
}
l 在C++类的成员变量被声明为static(称为静态成员变量),意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见;而类的静态成员函数也只能访问静态成员(变量或函数)。
l 类的静态成员变量必须在声明它的文件范围内进行初始化才能使用,private类型的也不例外。如,
float SavingsAccount::currentRate = 0.00154;
(注:currentRate是类SavingsAccount的静态成员变量)
register
l
用register声明的变量称着寄存器变量,在可能的情况下会直接存放在机器的寄存器中;但对32位编译器不起作用,当global
optimizations(全局优化)开的时候,它会做出选择是否放在自己的寄存器中;不过其它与register关键字有关的其它符号都对32位编译
器有效。
auto
l 它是存储类型标识符,表明变量(自动)具有本地范围,块范围的变量声明(如for循环体内的变量声明)默认为auto存储类型。
extern
l
声明变量或函数为外部链接,即该变量或函数名在其它文件中可见。被其修饰的变量(外部变量)是静态分配空间的,即程序开始时分配,结束时释放。用其声明的
变量或函数应该在别的文件或同一文件的其它地方定义(实现)。在文件内声明一个变量或函数默认为可被外部使用。
l 在C++中,还可用来指定使用另一语言进行链接,这时需要与特定的转换符一起使用。目前Microsoft C/C++仅支持”C”转换标记,来支持C编译器链接。使用这种情况有两种形式:
u extern “C” 声明语句
u extern “C” { 声明语句块 }
volatile
l 限定一个对象可被外部进程(操作系统、硬件或并发线程等)改变,声明时的语法如下:
int volatile nVint;
这样的声明是不能达到最高效的,因为它们的值随时会改变,系统在需要时会经常读写这个对象的值。 只常用于像中断处理程序之类的异步进程进行内存单元访问。
const
l &n
bsp; const所修饰的对象或变量不能被改变,修饰函数时,该函数不能改变在该函数外面声明的变量也不能调用任何非const函数。在函数的声明与定义时都要加上const,放在函数参数列表的最后一个括号后。
l
在C++中,用const声明一个变量,意味着该变量就是一个带类型的常量,可以代替#define,且比#define多一个类型信息,且它执行内链
接,可放在头文件中声明;但在C中,其声明则必须放在源文件(即.C文件)中,在C中const声明一个变量,除了不能改变其值外,它仍是一具变量,如
const int maxarray = 255;
char store_char[maxarray]; //C++中合法,C中不合法
l const修饰指针时要特别注意。例:
char *const aptr = mybuf; // 常量指针
*aptr = 'a'; // Legal
aptr = yourbuf; // Error
const char *bptr = mybuf; // (指针bptr)指向常量数据
*bptr = 'a'; // Error
bptr = yourbuf; // Legal
l const修饰成员函数时不能用于构造和析构函数。
=====================================================
【转载】extern用法详解
cn.baddog posted @ 2007年07月04日 07:57PM
in
半吊程序 with tags
c c++ extren gougou 很早的 东东 1 基本解释
extern可以置于变量或者前,以标示变量或者的定义在别的文件中,提示编译器遇到此变量和时在其他模块中寻找其定义。
另外,extern也可用来进行链接指定。
2 问题:extern 变量
在一个源文件里定义了一个数组:char a[6];
在另外一个文件里用下列语句进行了声明:extern char
*a;
请问,这样可以吗?
答案与分析:
1)、不可以,程序运行时会告诉你非法访问。原因在于,指向类型T的并不等价于类型T的数组。extern char
*a声明的是一个变量而不是字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为extern char
a[ ]。
2)、例子分析如下,如果a[] = “abcd”,则外部变量a=0×61626364 (abcd的ASCII码值),*a显然没有意义
显然a指向的空间(0×61626364)没有意义,易出现非法内存访问。
3)、这提示我们,在使用extern时候要严格对应声明时的格式,在实际编程中,这样的错误屡见不鲜。
4)、extern用在变量声明中常常有这样一个作用,你在*.c文件中声明了一个全局的变量,这个全局的变量如果要被引用,就放在*.h中并用extern来声明。
4
问题:extern 2
当提供方单方面修改原型时,如果使用方不知情继续沿用原来的extern申明,这样编译时编译器不会报错。但是在运行过程中,因为少了或者多了输入参数,往往会照成系统错误,这种情况应该如何解决?
答案与分析:
目前业界针对这种情况的处理没有一个很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供对外部的声明,然后调用方include该头文件,从而省去extern这一步。以避免这种错误。
宝剑有双锋,对extern的应用,不同的场合应该选择不同的做法。
5 问题:extern “C”
在环境下使用C的时候,常常会出现编译器无法找到obj模块中的C定义,从而导致链接失败的情况,应该如何解决这种情况呢?
答案与分析:
语言在编译的时候为了解决的多态问题,会将名和参数联合起来生成一个中间的名称,而则不会,因此会造成链接时找不到对应的情况,此时C就需要用extern
“C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间名。
下面是一个标准的写法:
//在.h文件的头上
#ifdef __cplusplus
#if __cplusplus
extern
“C”{
#endif
#endif /* __cplusplus */
…
…
//.h文件结束的地方
#ifdef
__cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */
3 问题:extern 1
常常见extern放在的前面成为声明的一部分,那么,的关键字extern在的声明中起什么作用?
答案与分析:
如果的声明中带有关键字extern,仅仅是暗示这个可能在别的源文件里定义,没有其它作用。即下述两个声明没有明显的区别:
extern int f(); 和int f();
当然,这样的用处还是有的,就是在程序中取代include “*.h”来声明,在一些复杂的项目中,我比较习惯在所有的声明前添加extern修饰。
=========================================
作者:宋宝华 e-mail:21cnbao@21cn.com 出处:太平洋电脑网
1.引言
C++语言的创建初衷是 “a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。
2.从标准头文件说起
某企业曾经给出如下的一道面试题:
面试题
为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
分析
显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。
那么
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
的作用又是什么呢?我们将在下文一一道来。
3.深层揭密extern "C"
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。
(1) 被extern "C"限定的函数或变量是extern类型的;
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:
extern int a;
仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。
(2) 被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;
未加extern “C”声明时的编译方式
首先看看C++中对类似C的函数是怎样编译的。
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
未加extern "C"声明时的连接方式
假设在C++中,模块A的头文件如下:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模块B中引用该函数:
// 模块B实现文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!
加extern "C"声明后的编译和连接方式
加extern "C"声明后,模块A的头文件变为:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。
所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):
实现C++与C及其它语言的混合编程。
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。
4.extern "C"的惯用法
(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
extern "C"
{
#i nclude "cExample.h"
}
而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。
笔者编写的C++引用C函数例子工程中包含的三个文件的源代码如下:
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c语言实现文件:cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#i nclude "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。
(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
笔者编写的C引用C++函数例子工程中包含的三个文件的源代码如下:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#i nclude "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
/* 这样会编译出错:#i nclude "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
如果深入理解了第3节中所阐述的extern "C"在编译和连接阶段发挥的作用,就能真正理解本节所阐述的从C++引用C函数和C引用C++函数的惯用法。对第4节给出的示例代码,需要特别留意各个细节。
《另一篇文章》
时常在cpp的代码之中看到这样的代码:
#ifdef __cplusplu*
**tern "C" {
#endif
//一段代码
#ifdef __cplusplus
}
#endif
这样的代码到底是什么意思呢?首先,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{和}处理其中的代码。
要明白为何使用extern "C",还得从cpp中对函数的重载处理开始说起。在c++中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理,加入比如函数的返回类型等等.而在C中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++和C对产生的函数名字的处理是不一样的.
比如下面的一段简单的函数,我们看看加入和不加入extern "C"产生的汇编代码都有哪些变化:
int f(void)
{
return 1;
}
在加入extern "C"的时候产生的汇编代码是:
.file "test.cxx"
.text
.align 2
.globl _f
.def _f; .scl 2; .type 32; .endef
_f:
pushl %ebp
movl %esp, %ebp
movl $1, %eax
popl %ebp
ret
但是不加入了extern "C"之后
.file "test.cxx"
.text
.align 2
.globl __Z1fv
.def __Z1fv; .scl 2; .type 32; .endef
__Z1fv:
pushl %ebp
movl %esp, %ebp
movl $1, %eax
popl %ebp
ret
两段汇编代码同样都是使用gcc -S命令产生的,所有的地方都是一样的,唯独是产生的函数名,一个是_f,一个是__Z1fv。
明白了加入与不加入extern "C"之后对函数名称产生的影响,我们继续我们的讨论:为什么需要使用extern "C"呢?C++之父在设计C+ +之时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好C库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。
试想这样的情况:一个库文件已经用C写好了而且运行得很良好,这个时候我们需要使用这个库文件,但是我们需要使用C++来写这个新的代码。如果这个代码使用的是C++的方式链接这个C库文件的话,那么就会出现链接错误.我们来看一段代码:首先,我们使用C的处理方式来写一个函数,也就是说假设这个函数当时是用C写成的:
//f1.c
extern "C"
{
void f1()
{
return;
}
}
编译命令是:gcc -c f1.c -o f1.o 产生了一个叫f1.o的库文件。再写一段代码调用这个f1函数:
// test.cxx
//这个extern表示f1函数在别的地方定义,这样可以通过
//编译,但是链接的时候还是需要
//链接上原来的库文件.
extern void f1();
int main()
{
f1();
return 0;
}
通过gcc -c test.cxx -o test.o 产生一个叫test.o的文件。然后,我们使用gcc test.o f1.o来链接两个文件,可是出错了,错误的提示是:
test.o(.text + 0x1f):test.cxx: undefine reference to 'f1()'
也就是说,在编译test.cxx的时候编译器是使用C++的方式来处理f1()函数的,但是实际上链接的库文件却是用C的方式来处理函数的,所以就会出现链接过不去的错误:因为链接器找不到函数。
因此,为了在C++代码中调用用C写成的库文件,就需要用extern "C"来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它们。
比如,现在我们有了一个C库文件,它的头文件是f.h,产生的lib文件是f.lib,那么我们如果要在C++中使用这个库文件,我们需要这样写:
extern "C"
{
#i nclude "f.h"
}
回到上面的问题,如果要改正链接错误,我们需要这样子改写test.cxx:
extern "C"
{
extern void f1();
}
int main()
{
f1();
return 0;
}
重新编译并且链接就可以过去了.
总结
C和C++对函数的处理方式是不同的.extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明。