(2)register
当声明对象有自动生存周期时,可以使用register修饰符。因此,register也只能用在函数内的声明中。
此关键字告诉编译器:此对象的存取应该尽量快,最好存储在CPU的寄存器中。然而,编译器不见得会这么做。
另外要注意的是,当一个对象声明为register,就不可使用地址运算符&了,因为它有可能被放到寄存器中。
1、先说register吧
在c++中:
(1)register 关键字无法在全局中定义变量,否则会被提示为不正确的存储类。
(2)register 关键字在局部作用域中声明时,可以用 & 操作符取地址,一旦使用了取地址操作符,被定义的变量会强制存放在内存中。
在c中:
(1)register 关键字可以在全局中定义变量,当对其变量使用 & 操作符时,只是警告“有坏的存储类”。
(2)register 关键字可以在局部作用域中声明,但这样就无法对其使用 & 操作符。否则编译不通过。
3333333333333333333333333333333333333333
变量作用域和生存周期
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
C++中变量的生存周期
在C++中变量有以下两种生存周期:
#变量由编译程序在编译时给其分配存储空间(称为静态存储分配),并在程序执行过程中始终存在。这类变量的生存周期与程序的运行周期相同,当程序运行时,该变量的生存周期随即存在,程序运行结束,变量的生存周期随即终止。
#变量由程序在运行时自动给其分配存储空间(称为自动存储分配),这类变量为函数(或块)中定义的自动变量。它们在程序执行到该函数(或块)时被创建,在函数(或块)执行结束时释放所占用的空间。
注:在C++中,当标识符的作用域发生重叠时,在一个函数(或块)中声明的标识符可以屏蔽函数(或块)外声明的标识符或全局标识符。
>变量作用域示例
- #include<iostream>
- using namespace std;
- int i=0; //line 0
- int main(){
- int i=1; //line 1
- cout<<"i="<<i; //line 2
- { //line 3
- int i=2; //line 4
- cout<<"i="<<i; //line 5
- { //line 6
- i+=1; //line 7
- cout<<"i="<<i; //line 8
- } //line 9
- cout<<"i="<<i; //line 10
- } //line 11
- cout<<"i="<<i<<endl; //line 12
- system("pause");
- return 0; //line 13
- }
>程序的运行结果是:
>分析:
在函数外面定义的全局变量i(0行),它的作用域应为整个程序。在main函数开头处定义的局部变量i(1行),它的作用域为整个函数,即从1行到13行,根据上面标识符作用域冲突规定,在2行的输出语句将输出定义在1行的变量i的值,即为1。在4行定义的局部变量i,其作用域为所在块,即从4到10行,同样根据标识符作用域冲突规定,在5行的输出语句将输出定义在4行的变量i的值,即为2;同时由于7行所操作的变量i正处于定义在4行的变量i的作用范围内,因此将其值加1,得i=3,所以8行的输出语句输出变量i的值为3。同理,由于10行所输出的变量i正处于定义在4行变量i的作用域范围内,因此输出结果为3(其值在7行修改)。而在12行的输出语句所输出的变量i处于定义在1行变量i的左右范围内,因此输出结果为1(其值未被修改过)。
由于作用域的屏蔽效应,如果函数中有同名变量,则不能访问外部变量。为了能在函数内部访问函数外部定义的变量,可以使用C++中的作用域运算符“ :: ”。通过作用域运算符,即使该函数(或块)中已有与之同名的变量,也可以在函数(块)中使用定义在函数(块)外的全局变量。此外作用域运算符还可以用来指定类成员变量或成员函数所属的类。
当程序较大时,利用名字屏蔽机制是非常必要的。但是,这也会导致程序的可读性变差,好的程序设计风格应尽量避免名字屏蔽。
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
1、局部变量
(1)作用域函数内部,从变量定义完成到达函数结束
(2)生命周期变量定义完成 到 函数调用结束
(3)初始化值随机值
(4)内存区域栈(后进先出,TOP指针,高地址向低地址生长,连续)
2、全局变量
(1)作用域整个程序(本文件,和同工程其他文件都可访问)
(2)生命周期进程建立,main调用之前 到 进程结束(特别长)
(3)初始化值0
(4)内存区域全局变量区(数据段 .date)
static,可将全局变量可见性,限定在本文件内
可将函数可见性,限定在本文件内
extern,将外部的文件中的全局变量、函数引入到本文件中,本文件
可使用外部的全局变量
3、复合语句块变量
(1)作用域复合语句块内部,从变量定义完成到达语句块结束
(2)生命周期 变量定义完成 到 函数调用结束
(3)初始化值随机值
(4)内存区域栈
4、静态变量
静态局部变量:
(1)作用域局部变量相同,函数内部,从变量定义完成到达函数结束
(2)生命周期本文件内进程建立,main调用之前 到 进程结束
(3)初始化值0
(4)内存区域全局变量区/静态变量区/数据段/.data
静态全局变量:
(1)作用域从变量定义完成时开始,整个文件可访问。但是:只能在本文件内访
问,其他文件无法访问
(2)生命周期进程建立到结束(特别长)
(3)初始化值0
(4)内存区域全局变量区/静态变量区/数据段/.data
5、外部全局变量
(1)作用域整个程序,本文件,同工程的其他文件可用
(2)生命周期进程建立到结束(特别长)
(3)初始化值0
(4)内存区域全局变量区
6、形式参数变量
(1)作用域函数内部,函数调用时可见,到达函数结束
(2)生命周期函数调用 到 函数调用结束
(3)初始化值来在于调用函数传递的实参值
(4)内存区域栈
7、堆空间(char *p = (char *)malloc(4);new)
(1)作用域动态空间
(2)生命周期start:主动调用malloc,calloc,realloc(new),申请成功时建立
end:主动调用free,堆空间释放
end:进程结束,操作系统进行内存空间回收
(3)初始化值malloc为随机值;calloc为0
(4)内存区域堆(heap,从低向高,物理上不连续)
8、寄存器变量
(1)作用域
(2)生命周期该寄存器值被其他值替换
(3)初始化值
(4)内存区域放在寄存器当中,不在内存中出现
WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
44444444444444444444444444444444444444444444444444444
5555555555555555555555555555555555555555555555555555555
- int func1(void); //func1具有外部链接;
- int a = 10; //a具有外部链接,静态生存周期;
- extern int b = 1; //b具有外部链接,静态生存周期。但编译会有警告extern变量不应初始化,同时也要注意是否会重复定义;
- static int c; //c具有内部链接,静态生存周期;
- static int e; //e具有内部链接,静态生存周期;
- static void func2(int d){ //func2具有内部链接;参数d具有无链接,自动生存周期;
- extern int a; //a与上面的a一样(同一变量),具有外部链接,静态生存周期。注意这里的不会被默认初始为0,它只是个声明;
- int b = 2; //b具有无链接,自动生存同期。并且将上面声明的b隐藏起来;
- extern int c; //c与上面的c一样,维持内部链接,静态生存周期。注意这里的不会被默认初始为0,它只是个声明;
- //如果去掉了extern修饰符,就跟b类似了,无链接,自动生存周期,把上面声明的c隐藏起来;
- static int e; //e具有无链接,静态生存周期。并且将上面声明的e隐藏起来;初始化值为0;
- static int f; //f具有无链接,静态生存周期;
- }
#ifdef NOSTRUCTASSIGN
memcpy (d, s, l)
{
register char *d;
register char *s;
register int i;
while (i--)
*d++ = *s++;
}
#endif
666666666666666666666666666使用register修饰符的注意点
但是使用register修饰符有几点限制。
首先,register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
其次,因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。
在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。
早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。
下面是volatile变量的几个例子
1)并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3)多线程应用中被几个任务共享的变量
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
1)一个参数既可以是const还可以是volatile吗?解释为什么。
2); 一个指针可以是volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3)这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT