C++ 域和生命期(二):局部对象

在局部域中的变量声明引入了局部对象(local object),有三种局部对象: 自动对象(automatic object)、寄存器对象( register object)以及局部静态对象(local static object)。  区分这些对象的是对象所在存储区的属性和生命期。自动对象所在存储区从声明它的函数被调用时开始,一直到该函数结束为止;寄存器对象是一种自动对象,它支持对其值的快速存
取;局部静态对象的存储区在该程序的整个执行期间一直存在。 

一、自动对象

未初始化的自动对象包含一个随机的位模式,是该存储区上次被使用的结果,它的值被称为未指定的(unspecified)。

因为与自动对象相关联的存储区在函数结束时被释放,所以应该小心使用自动对象的地址,自动对象的地址不应该被用作函数的返回值,因为函数一旦结束了,该地址就指向一个无效的存储区。例如:
#include "Matrix.h"
 
Matrix* trouble( Matrix *pm )
{
{
Matrix res;
// 用 pm 做一些事情
// 把结果赋值给 res
return &res; // 糟糕!
}
 
int main()
{
Matrix m1;
// ...
Matrix *mainResult = trouble( &m1 );
// ...
}

mainResult 被设置为自动 Matrix对象res 的地址。不幸的是 res 的存储区在trouble()完成时被释放,在返回到 main()时 mainResult指向一个未分配的内存;在本例中,该地址可能仍然有效,因为我们还没有调用其他函数覆盖掉 trouble()函数的活动记录的部分或全部,所以这样的错误很难检测 在main()中的后续代码部分使用 mainResult 会产生意想不到的结果。 

但是,把 main()的自动变量 m1的地址传递给函数 trouble()则是安全的,我们可以保证在 trouble()调用期间 main()的存储区在栈中一直是有效的,因此 m1的内存区在 trouble() 调用期间都是可被访问的 。

当一个自动变量的地址被存储在一个生命期长于它的指针时,该指针被称为空悬指针(dangling pointer), 这是一个严重的程序员错误,因为它所指的内容是不可预测的,如果该地址的值正好合适,因此程序就不会产生段错误,该程序可能一直执行到完成 但是给出的是一个无效的结果。

二、寄存器自动对象

在函数中频繁被使用的自动变量可以用 register 声明,如果可能的话,编译器会把该对象装载到机器的寄存器中,如果不能够的话,则对象仍位于内存中,出现在循环语句中的数组索引和指针是寄存器对象的很好例子 :
for ( register int ix = 0; ix < sz; ++ix ) // ...
for (register int *p = array ; p < arraySize; ++p ) // ...
函数参数也可以被声明为寄存器变量 
bool find( register int *pm, int val ) {
while ( *pm )
  if ( *pm++ == val ) return true;

return false;
}
 

如果所选择的变量被频繁使用,则寄存器变量可以提高函数的执行速度 ;关键字 register 对编译器来说只是一个建议,有些编译器可能忽略该建议,而是使用寄存器分配算法找出最合适的候选放到机器可用的寄存器中,因为编译器知道运行该程序的机器的结构。所以它选择寄存器的内容时常常会做出更有意义的决定 。

三、静态局部对象

我们也能够在函数定义或者函数定义的复合语句中,声明可在整个程序运行期间一直存在的局部对象;当一个局部变量的值必须在多个函数调用之间保持有效时;我们不能使用普通的自动对象,自动对象的值在函数结束时被丢弃。

 

这种情形的一种解决方案是把局部对象声明为static,静态局部对象具有静态存储持续期间(static storage duration )或静态范围 (static extent ),虽然它的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内,静态局部对象在程序执行到该对象的声明处时被首次初始化。例如,下面是 gcd()的一个版本,它占用一个静态局部对象来跟踪递归的深度。 
#include <iostream>
 
int traceGcd( int v1, int v2 )
{
static int depth = 1;
cout << "depth #" << depth++ << endl;
if ( v2 == 0 ) {
  depth = 1;
  return v1;
}
return traceGcd( v2, v1%v2 );
}
与静态局部对象 depth相关联的值在traceGcd()的调用之间保持有效,初始化只在traceGcd()首次被调用时执行一次,下面的小程序使用了 traceGcd() 
#include <iostream>
 
extern int traceGcd(int, int);
int main() {
int rslt = traceGcd( 15, 123 );
cout << "gcd of (15,123): " << rslt << endl;
return 0;
}

编译并运行该程序 产生下列输出 
depth #1
depth #2
depth #3
depth #4
gcd of (15,123): 3

 

未经初始化的静态局部对象会被程序自动初始化为 0。相反,自动对象的值会是任意的 。除非它被显式初始化,下面的程序说明了自动和静态局部变量的缺省初始化以及不初始化自动对象的危险 。

#include <iostream>
 
const int iterations = 2;
 
void func() {
int value1, value2; // 未初始化
static int depth; // 隐式初始化为 0
if ( depth < iterations )
  { ++depth; func(); }
else depth = 0;
cout << "\nvalue1:\t" << value1;
cout << "\tvalue2:\t" << value2;
cout << "\tsum:\t" << value1 + value2;
}
 
int main() {
for ( int ix = 0; ix < iterations; ++ix ) func();
return 0;
}

执行后结果如下 
value1: 0 value2: 74924 sum: 74924
value1: 0 value2: 68748 sum: 68748
value1: 0 value2: 68756 sum: 68756
value1: 148620 value2: 2350 sum: 150970
value1: 2147479844 value2: 671088640 sum: - 1476398812
value1: 0 value2: 68756 sum: 68756

注意:value1和 value2是未经初始化的自动对象,它们的初始值如程序输出所示,完全是个随机值,因此求和的结果也是不能预测的。但是,即使 depth没有被初始化,它的值也会被保证是0 ,保证 func()递归地调用它自己两次 。

原文链接: https://www.cnblogs.com/charley_yang/archive/2010/12/19/1910819.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    C++ 域和生命期(二):局部对象

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/18970

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 下午7:55
下一篇 2023年2月7日 下午7:55

相关推荐