全局变量和全局静态变量
使用vc栈回溯发现
这里构造的栈结构
因为atexit的参数的c约定回调,而析构是thiscall,调用约定,所以内部必须包含一层E2才可以,在使用E2调用析构,而不是直接注册析构
atexit可以注册多个回调,而这些会是一个线性表,里面储存了你注册的函数地址.当main函数结束的时候会调用
1.会在ininterm里面进行初始化动作
2.会产生代理函数,这个代理函数是为了使ininterm函数的代码正常初始化而产生的一个统一接口的函数,暂且称为E4 (名字可能不一样)
3.E4函数代理是为了统一接口,其内部又调用了 构造函数代理 (E1),和析构函数代理(E3)
4.E1代理函数是为了统一参数用的,其内部是调用构造的,如果是有参数构造,则在E1代理函数内部可以看到传参的。
5.E3代理函数是为了注册析构函数的,为了使atexit函数正常运行而注册的(atexit和ininterm类似,一个从前往后,一个从后往前)
6.E2是E3内部给atexit函数注册的回调,这样在析构的时候则调用E2即可.
7.E2函数内部是真正的调用析构的
静态局部变量
第一次调用会从teb里的tls区域取出标志位
调用构造并同时注册函数
如果是第二次则会检查标志位,如果有标志位则跳过这个区域
对象传参
特征
1.函数外会调用拷贝构造
- 如果没有默认的拷贝构造
- 编译器会做串操作指令拷贝到栈顶
2.this指针在栈顶(有默认的拷贝构造)
- 汇编语句:mov ecx, esp
3.函数内会调用析构
- 基于参数的析构
代码示例
class MyTest
{
public:
MyTest(int nNumber)
{
m_nAge = nNumber;
}
MyTest(MyTest& obj)
{
m_nAge = obj.m_nAge;
}
~MyTest()
{
printf("~MyTest()");
}
public:
int m_nAge;
int m_nclass[50];
};
MyTest Test2(8);
void ShowClass(MyTest test)
{
printf("%d", test.m_nAge);
return;
}
int main(int argc)
{
MyTest Test(argc);
ShowClass(Test); //结构体传参调用拷贝构造
return 0;
}
函数外
函数内
对象作返回值
特征
1.继承结构体特征(它会默认给我们生成一个参数传入,如果有别的参数,这个参数默认在第一个)
2.函数内构造,函数外析构(有临时对象会构造两个,析构两次)
区分是否产生临时对象
1.不产生临时对象
- 如果为引用时,生命周期和作用域和引用一样MyTest &t = Getobj(int)
lass MyTest
{
public:
MyTest(int nNumber)
{
m_nAge = nNumber;
}
MyTest(const MyTest& obj)
{
m_nAge = obj.m_nAge;
}
~MyTest()
{
printf("~MyTest()");
}
public:
int m_nAge;
int m_nclass[50];
};
MyTest GetObj(int nNumber)
{
MyTest test(nNumber);
return test;
}
int main(int argc)
{
MyTest Test = GetObj(argc); //直接用返回值定义变量 此处不产生临时变量
printf("%d", Test.m_nAge);
return 0;
}
2.产生临时对象
- 临时对象会在函数内构造
- 出函数遇到第一个分号之前析构
class MyTest
{
public:
MyTest(int nNumber)
{
m_nAge = nNumber;
}
MyTest(const MyTest& obj)
{
m_nAge = obj.m_nAge;
}
~MyTest()
{
printf("~MyTest()");
}
public:
int m_nAge;
int m_nclass[50];
};
MyTest GetObj(int nNumber)
{
MyTest test(nNumber);
return test;
}
int main(int argc)
{
MyTest Test(argc);
Test = GetObj(argc + 5); //此处会产生临时变量
printf("%d", Test.m_nAge);
return 0;
}
堆对象
特征
- 开始会调用new申请空间,并伴随一段大跳
1.1 如果申请到空间则执行构造(返回值为1)
1.2 如果没申请到空间则不执行构造(返回值为0)
2.调用delete的时候,会传参1并调用代理析构函数(参数1可以表明是一个堆对象)
3.如果代理析构函数参数为0,则表明是手动调用析构,此时不会释放空间Test.~MyTest()
class MyTest
{
public:
MyTest()
{
m_nAge = 5;
printf("MyTest()");
}
MyTest(const MyTest& obj)
{
m_nAge = obj.m_nAge;
}
~MyTest()
{
printf("~MyTest()");
}
public:
int m_nAge;
int m_nclass[50];
};
int main(int argc)
{
MyTest* Test = new MyTest;
printf("%d", Test->m_nAge);
delete Test;
return 0;
}
函数外
函数内
对象数组
代码示例
class MyTest
{
public:
MyTest()
{
m_nAge = 5;
printf("MyTest()");
}
MyTest(const MyTest& obj)
{
m_nAge = obj.m_nAge;
}
~MyTest()
{
printf("~MyTest()");
}
public:
int m_nAge;
int m_nclass[50];
};
int main(int argc)
{
MyTest Test[5];
printf("%d", Test[0].m_nAge);
return 0;
}
1.定义的时候调用构造迭代器代理函数
- 参数1:首地址
- 参数2:每个类的大小
- 参数3:类个数
- 参数4:构造函数指针
- 参数5:析构函数指针
2.析构的时候调用析构迭代器代理函数(相比构造少一个参数)
3.代理函数内循环调用构造/析构 - 如果是构造,循环执行顺序按数组下标升序
- 如果是析构,循环执行顺序按数组下标降序
堆对象数组
代码示例
class MyTest
{
public:
MyTest()
{
m_nAge = 5;
printf("MyTest()");
}
MyTest(const MyTest& obj)
{
m_nAge = obj.m_nAge;
}
~MyTest()
{
printf("~MyTest()");
}
public:
int m_nAge;
int m_nclass[50];
};
int main(int argc)
{
MyTest* Test = new MyTest[5];
Test.~MyTest();
printf("%d", Test[0].m_nAge);
delete[] Test;
return 0;
}
1.开始会调用new申请空间,并伴随一段大跳
- 如果申请到空间则执行构造(返回值为1)
- 如果没申请到空间则不执行构造(返回值为0)
2.申请到空间,执行代理函数
- 参数1:首地址
- 参数2:每个类的大小
- 参数3:类个数
- 参数4:构造函数指针
- 参数5:析构函数指针
3.调用delete的时候,会传参3并调用代理析构函数(参数3可以表明是一个堆对象数组)
- 代理函数外
- 代理函数内