函数调用中编译器的循环代码优化
namespace _nmspl {
__int64 mytest(int mv){
__int64 icout = 0;
for(int i = 0; i<1000000; i++){
icout += 1;
}
return icout;
}
void func(){
clock_t start, end;
__int64 mycout = 1;
start = clock();
for(int i = 0; i <= 1000; i++){
mycout += mytest(6); //固定值
}
end = clock();
cout << end - start <
}
int main(){
_nmspl::func();
return 1;
}
// 编译版本分为release Debug 版本
// release大幅度优化代码
// Debug : 这个代码运行时间大概2300 毫秒左右
// Release : 0 -1 毫秒
for(int i = 0; i <= 1000; i++){
mycout += mytest(6);
}
编译器可能对这段含有固定值的代码进行了优化成1行
一千次循环mytest(6)的和
------------------------------------------------
for(int i = 0; i<1000000; i++){
icout += 1;
}
icout += 循环多少次之后的和值
----------------------------------------------------
namespace _nmspl {
__int64 mytest(int mv){
__int64 icout = 0;
for(int i = 0; i<1000000; i++){
icout += 1;
}
return icout;
}
void func(){
clock_t start, end;
__int64 mycout = 1;
start = clock();
for(int i = 0; i <= 1000; i++){
mycout += mytest(i); //非固定值
}
end = clock();
cout << end - start <
}
// 这时候运行时间差不多
所以 编译器将一些固定参数的函数调用视为一种不变的表达式
指向成员函数的指针 以及 vcall
class A{
public:
void myfunc(int value){}
void myfunc2(int value){}
static void staticfunc(int value){}
virtual void func(){}
}
int main (){
A mya;
void (A::*point)(int value) = &A::myfunc;
(mya.*point)(15);
A *pointA = new A();
(pointA->*point)(12);
void (*staticpoint)(int value) = &A::staticfunc;
}
vcall ( virtual call 需调用
代表一段要执行的代码地址 指端代码引导去执行正确的虚函数
或者直接把vcall看成虚函数表, 如果这么看的话 vcall[0] 代表的是虚函数表里的第一个函数
vcall[4] 代表虚函数表里的第二个函数
&A::myvirtualfunc 打印出来的是一个地址 这个地址中有一段代码
这个代码中记录的是该虚函数在虚函数表中的一个偏移值
class A{
public:
void myfunc(int value){}
void myfunc2(int value){}
static void staticfunc(int value){}
virtual void func(){}
}
class B: public A{
virtual void func(){}
}
int main(){
void (B::*pointb)() = &A::func;
pointb->func();
//即使这里使用的是A的func地址但是他的vcall偏移量还B的偏移量 所以调用的是B的func;
}
使用inline以后 编译器内部会一个比较复杂的测试算法来评估这个inline函数的复杂度
会统计这个inline函数中 赋值次数 内部函数调用 虚函数调用等 ---- 权重
开发者写inline只是对编译器的一个建议, 如果比那一起评估这个inline函数权重复杂度过高这个inline就会被编译器忽略
如果inline被编译器采纳,那么inline的扩展就要在调用这个inline的函数点上进行
可能带来额外的问题 比如参数求职 可能产生临时对象
形参会被对象的实参取代
编译器会先求职 然后用实参在替换形参
inline函数中局部对象能少用就少用
----------------------------------------
class A{
public:
int i;
int j;
}
int main(){
A a;
a.i = 12;
a.j = 14;
A a2 = a; // 执行拷贝构造函数
a2.i = 15;
a2.j = 18;
A a3;
a3=a2; // 执行拷贝赋值运算符
}
// 如果我们不写自己ide拷贝构造函数和拷贝赋值运算符
编译及也会有默认的对象拷贝和对象赋值行为
----------------------------------------
class A{
public:
int i;
int j;
A & operator=(const A& tmp){ // 拷贝赋值运算符
i = tmp.i;
j = tmp.j;
return *this;
}
A(const A& tmp){ // 拷贝构造函数
i = tmp.i;
j = tmp.j;
}
A(){};
};
int main(){
A a;
a.i = 12;
a.j = 14;
A a2 = a; // 执行拷贝构造函数
a2.i = 15;
a2.j = 18;
A a3;
a3=a2; // 执行拷贝赋值运算符
}
当我们提供自己的拷贝赋值运算符 我们就结构了系统默认的拷贝行为
此时我们有责任在拷贝复制运算符和拷贝复制运算符中完成对象的拷贝和赋值
----------------------------------------
如何禁止对象的拷贝构造和赋值 : 把拷贝构造函数和拷贝赋值运算符私有起来 不需要些函数体
----------------------------------------
析构函数被合成
1. 如果继承一个基类,基类中带析构函数,那么编译器就会合成一个析构函数来调用基类析构函数
2. 如果类成员是一个类类型成员,并且这个成员带析构函数,编译器也会合成一个析构函数
析构函数被扩展
如果我们有自己的析构函数那么编译器就会在适当的时候扩展我们的析构函数
1. 如果类成员是一个类类型成员 并且这个成员带析构函数,编译器也会扩展析构函数调用类成员的析构函数
先执行当前类的析构函数 然后执行类成员的析构函数
2.如果继承一个基类,基类中带析构函数,那么编译器就会扩展当前类析构函数来调用基类析构函数
局部对象会在离开函数体的时候在return中添加析构函数
所以定义中对象的时候就需要在用的时候再定义以面再上面定义如果中途 条件判断退出那这个就是多余对象-
全局对象对象会再main函数执行之前就被构造出来 可以在main中直接使用,再main函数执行完毕之后析构
全局对象不给初值的时候默认被清0
全局变量再编译阶段就会给空间分配出来
----------------------------------------
局部静态对象, 对象数组构造析构和内存分配
静态局部对象不管初调用多少次分配的地址都是相同的
局部静态对象 内存地址是再编译期间就确定好的
局部静态对象 的析构函数调用是在main函数执行完毕以后
编译器是如果确保这个静态局部对象值初始化一次
首先 编译器分配内存并不是单纯的只是分配固定的内存 他周围的内存会作为一种附属值用来记录一些这个内存的信息
其次 静态局部变量周围的内存会记录这个局部变量的具体信息。
静态对象数组内存地址也是再编译期间就确定好的
不管初调用多少次分配的地址都是相同的