C++中的生存期和作用域

标签:C++

再弄篇自己的老帖来。

生存期和作用域,究竟有什么样的关系呢?

先解释一下生存期吧。
一个进程对应的内存空间中,包含5种不同的数据区。
按照内存中从低到高的顺序,分别为:栈、堆、BSS段、数据段和代码段。
栈:存放程序临时创建的局部对象。一般VC++6.0生成的可执行文件只有1MiB多的栈。
堆:存放进程运行中被动态分配的内存段。它大小并不固定,可动态扩张或缩减。属于内存中最多的资源。
BSS段:(它是block started by symbol的缩写)存放程序中未初始化全局对象,操作系统将自动把BSS段全部置零。
数据段:存放已初始化的全局对象和静态对象。
代码段:存放可执行文件的操作指令,即可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以是只读的。
可以看出,只有代码段是被操作系统保护为只读的,其他地方的常量都只被编译器保护。
当然,内存中还有很多不属于数据区的,它们是不可读写的。
在栈中的数据,生存期为包含它的{};
在堆中的数据,生存期为它被free或delete释放前。
其他的数据,生存期为进程结束前。
用户定义的变量或常量,在生存期内,除非用户自行更改或被外界因素更改,否则不会被更改。

作用域则是可以直接显式使用该变量或常量的区域。
注:这只是我自己下的定义,这样堆中的数据应该是没有作用域的,因为它不能被直接显示使用(需要通过指针或引用)。
除了局部静态变量的作用域为为包含它的{}以外,其他和生存期相同。

下面做个简单的测试,写得很乱,看不下去的看结论就行了。
// File Name : life_test.cpp
// Author : keakon
// Create Date : 2006/5/26
// Last Edited Date : 2006/5/27
// 测试各种对象的生存期和作用域
// 在VC++6.0下编译运行通过
// 命名时,g表全局,l表局部,c表常量,s表静态,h表堆,r表寄存器
/////////////////////////////////////////
#include <iostream>
#include <string>

using std::cout;
using std::string;
/////////////////////////////////////////
class A
{
public:
 A(string const& name) : m_Name(name) {cout << m_Name << ".A::A()\n";}
 ~A() {cout << m_Name << ".A::~A()\n";}
 string const& getName() const {return m_Name;}
private:
 string m_Name;
};
/////////////////////////////////////////
A g("g");
const A g_c("g_c");
static A g_s("g_s");
const static A g_c_s("g_c_s");

const int NUM = 10; //存放指针数组的大小
/////////////////////////////////////////
void print(A const* p[])
{
 cout << "\n\n";
 for (int i = 0; i < NUM; ++i)
 {
  cout << p[i]->getName() << " :\t" << p[i] << '\n';
 }
 cout << "\n\n";
}
/////////////////////////////////////////
void printName(A const* p[])
{
 cout << "\n\n";
 for (int i = 0; i < NUM; ++i)
 {
#ifndef NDEBUG
//如果没有定义NDEBUG,跳过4、5、8和9,因为对象已被析构
//NDEBUG是VC++6.0的一个系统宏,在debug模式下未定义,在release模式下被定义
//可以看见release模式下,4、5、8和9号对象的name变成乱码
//若取消该宏,在debug模式下会出错
//这和2种模式对离开了生存期的变量的处理方式不同有关
//PS:若取消该宏,g++下可能会打出几屏幕乱码^^
  if (i == 4 || i == 5 || i == 8 || i == 9)
  {
   continue; //进入下一次for循环
  }
#endif
  cout << p[i]->getName() << " :\t" << p[i] << '\n';
 }
 cout << "\n\n";
}
/////////////////////////////////////////
void assign(A const* p[])
{
 A l("l");
 const A l_c("l_c");
 static A l_s("l_s");
 const static A l_c_s("l_c_s");
 register A r("r");

 p[0] = &g;
 p[1] = &g_c;
 p[2] = &g_s;
 p[3] = &g_c_s;
 p[4] = &l;
 p[5] = &l_c;
 p[6] = &l_s;
 p[7] = &l_c_s;
 p[8] = new A("l_h"); //不检查是否失败了,留给自己的人品去检验吧
 p[9] = &r;

 print(p);

 delete p[8];
}
/////////////////////////////////////////
namespace test
{
 const string str("keakon是帅哥=。=");

 const string& getStr()
 {
  return str;
 }

 void printStr()
 {
  cout << str << "\n\n\n";
 }
}
/////////////////////////////////////////
int main()
{
 A const* p[NUM] = {NULL}; //全赋值为NULL

 assign(p);
 printName(p);

 assign(p);
 printName(p);

 string const &pi = test::getStr(); //其实也可以用指针的,不过感觉引用更直观
 const_cast<string&>(pi) = "keakon是美女^^v"; //更改了常量,汗=。=
 test::printStr(); //输出“keakon是美女^^v”

 return 0;
}
结论(仅适用于VC++6.0):
注:下文我用“实体”来表示即可以是变量,也可以是常量。
1.全局实体有static属性(地址和static实体放在一起)
2.寄存器实体有auto属性(地址和auto实体放在一起)//忘记在哪本书上看见的,貌似不能对register实体取地址
3.局部非静态实体(包括常量实体、寄存器实体和堆实体)在脱离作用域后,实体的内容是随机的
4.静态实体在脱离作用域后(程序结束前),仍可以被正常访问,且内容保持不变
5.处于另一个namespace的实体也符合上述结论(这里只测试了全局实体)
6.全局或静态实体析构时,编译器可能已将其他非静态实体析构了(如cout),因此可能不会输出信息。(析构顺序在C++标准中是未定义的。)
实际上,如果全局或静态实体内有非静态的成员的话,在它析构前,它的这些成员就已经被析构了。

0条评论 你不来一发么↓

    想说点什么呢?