none
cout出现变量延迟解析,这是bug还是语言特性? RRS feed

  • 问题

  • int * getInt2(int a[]) {
    	(*a) *= 2;
    	return a;
    }
    
    int main(int argc,char *argv[])
    {
    	int intInit = 1;
    	int * (*intPtr)(int *) = getInt2;
    	cout << intInit++ << ":" << intInit << endl;
    	cout << intPtr(&intInit) << ":" << intInit<<endl;
    	cout << intInit << endl;
    	cout << *intPtr(&intInit) << endl;
    	cout << (*intPtr)(&intInit) << ":" << *(*intPtr)(&intInit) << endl;
    	cout << intInit << endl;
    }

    实际运行结果:

    1:2
    012FFABC:2
    4
    8
    012FFABC:16
    32

    而期望结果是:

    1:2
    012FFABC:4
    4
    8
    012FFABC:32
    32

    这个是我哪里理解有问题还是确实它出现bug了?感觉好像是bug。

    使用的版本是:

    visual studio 15.9.9 

    .net framework 4.7.03190
    2019年4月17日 9:33

答案

  • 为了简单起见,直接分析汇编码吧。

    	int intInit = 1;
    00E126B2  mov         dword ptr [intInit],1  
    	int * (*intPtr)(int *) = getInt2;
    00E126B9  mov         dword ptr [intPtr],offset getInt2 (0E11262h)  
    	cout << intInit++ << ":" << intInit << endl;
    00E126C0  mov         eax,dword ptr [intInit]  
    00E126C3  mov         dword ptr [ebp-0E0h],eax  
    00E126C9  mov         ecx,dword ptr [intInit]  
    00E126CC  add         ecx,1  
    00E126CF  mov         dword ptr [intInit],ecx  
    00E126D2  mov         esi,esp  
    00E126D4  push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    00E126D9  mov         edi,esp  
    00E126DB  mov         edx,dword ptr [intInit]  
    00E126DE  push        edx  
    00E126DF  push        offset string ":" (0E19B30h)  
    00E126E4  mov         ebx,esp  
    00E126E6  mov         eax,dword ptr [ebp-0E0h]  
    00E126EC  push        eax  
    00E126ED  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0E1D0DCh)]  
    00E126F3  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A4h)]  
    00E126F9  cmp         ebx,esp  
    00E126FB  call        __RTC_CheckEsp (0E1128Ah)  
    00E12700  push        eax  
    00E12701  call        std::operator<<<std::char_traits<char> > (0E1120Dh)  
    00E12706  add         esp,8  
    00E12709  mov         ecx,eax  
    00E1270B  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A4h)]  
    00E12711  cmp         edi,esp  
    00E12713  call        __RTC_CheckEsp (0E1128Ah)  
    00E12718  mov         ecx,eax  
    00E1271A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A8h)]  
    00E12720  cmp         esi,esp  
    00E12722  call        __RTC_CheckEsp (0E1128Ah)  
    	cout << intPtr(&intInit) << ":" << intInit << endl;
    00E12727  mov         esi,esp  
    00E12729  push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    00E1272E  mov         edi,esp  
    00E12730  mov         eax,dword ptr [intInit]  
    00E12733  push        eax  
    00E12734  push        offset string ":" (0E19B30h)  
    00E12739  mov         ebx,esp  
    00E1273B  lea         ecx,[intInit]  
    00E1273E  push        ecx  
    00E1273F  call        dword ptr [intPtr]  
    00E12742  add         esp,4  
    00E12745  cmp         ebx,esp  
    00E12747  call        __RTC_CheckEsp (0E1128Ah)  
    00E1274C  mov         ebx,esp  
    00E1274E  push        eax  
    00E1274F  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0E1D0DCh)]  
    00E12755  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A0h)]  
    00E1275B  cmp         ebx,esp  
    00E1275D  call        __RTC_CheckEsp (0E1128Ah)  
    00E12762  push        eax  
    00E12763  call        std::operator<<<std::char_traits<char> > (0E1120Dh)  
    00E12768  add         esp,8  
    00E1276B  mov         ecx,eax  
    00E1276D  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A4h)]  
    00E12773  cmp         edi,esp  
    00E12775  call        __RTC_CheckEsp (0E1128Ah)  
    00E1277A  mov         ecx,eax  
    00E1277C  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A8h)]  
    00E12782  cmp         esi,esp  
    00E12784  call        __RTC_CheckEsp (0E1128Ah)  


    cout << intInit++ << ":" << intInit << endl;和cout << intPtr(&intInit) << ":" << intInit << endl;这两句后面的输出流部分是一样的,所以咱们就看前面计算和压栈的这部分。

    	cout << intInit++ << ":" << intInit << endl;
    00E126C0  mov         eax,dword ptr [intInit]  
    00E126C3  mov         dword ptr [ebp-0E0h],eax  
    00E126C9  mov         ecx,dword ptr [intInit]  
    00E126CC  add         ecx,1  
    00E126CF  mov         dword ptr [intInit],ecx  
    00E126D2  mov         esi,esp  
    00E126D4  push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    00E126D9  mov         edi,esp  
    00E126DB  mov         edx,dword ptr [intInit]  
    00E126DE  push        edx  
    00E126DF  push        offset string ":" (0E19B30h)  
    00E126E4  mov         ebx,esp  
    00E126E6  mov         eax,dword ptr [ebp-0E0h]  
    	cout << intPtr(&intInit) << ":" << intInit << endl;
    00E12727  mov         esi,esp  
    00E12729  push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    00E1272E  mov         edi,esp  
    00E12730  mov         eax,dword ptr [intInit]  
    00E12733  push        eax  
    00E12734  push        offset string ":" (0E19B30h)  
    00E12739  mov         ebx,esp  
    00E1273B  lea         ecx,[intInit]  
    00E1273E  push        ecx  
    00E1273F  call        dword ptr [intPtr]  
    00E12742  add         esp,4  
    00E12745  cmp         ebx,esp  
    00E12747  call        __RTC_CheckEsp (0E1128Ah)  
    00E1274C  mov         ebx,esp  


    很显然在经过编译器优化之后,实际步骤和理论上的步骤不尽相同。cout << intInit++ << ":" << intInit << endl;这一部分是:

    mov         eax,dword ptr [intInit]
    mov         dword ptr [ebp-0E0h],eax  //这一句和上一句是将变量intInit赋值给匿名变量ebp-0E0h
    mov         ecx,dword ptr [intInit]  //变量intInit存入寄存器ecx
    add         ecx,1  //等于是++intInit
    mov         dword ptr [intInit],ecx  //ecx中的计算结果返回给变量intInit
    mov         esi,esp  
    push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    mov         edi,esp  
    mov         edx,dword ptr [intInit]  //将经过++后的intInit变量压栈
    push        edx  
    push        offset string ":" (0E19B30h)  
    mov         ebx,esp  
    mov         eax,dword ptr [ebp-0E0h]  //将匿名变量ebp-0E0h压栈

    因此上面的代码相当于:

    auto temp = intInit;
    ++intInit;
    cout << temp << ":" << intInit << endl;

    这个和之前的理论预测完全相同。

    接下来再看cout << intPtr(&intInit) << ":" << intInit << endl;这部分。

    mov         esi,esp  
    push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  //直接开始压栈
    mov         edi,esp  
    mov         eax,dword ptr [intInit]  //将intInit变量压栈
    push        eax  
    push        offset string ":" (0E19B30h)  
    mov         ebx,esp  
    lea         ecx,[intInit]  
    push        ecx  //将intInit变量的地址压栈
    call        dword ptr [intPtr]  //通过函数指针调函数
    add         esp,4  
    cmp         ebx,esp  
    call        __RTC_CheckEsp (0E1128Ah)  
    mov         ebx,esp 

    这段代码相当于:

    cout << ":" << intInit << endl;
    intPtr(&intInit);
    cout << &intInit;

    为什么会这样呢?因为函数的返回值是一个值,不是计算语句,因此cout << intPtr(&intInit) << ":" << intInit << endl;这句话中实际上没有任何计算,所以直接就开始压栈了,没有计算过程,遇到了函数的时候就call一下,反正函数栈对当前栈没影响。如果你把前面cout << intPtr(&intInit) << ":" << intInit << endl;这句改换成cout << ++(*intPtr(&intInit)) << ":" << intInit << endl;再运行就会得到5:5了。所以结论就是函数是一个指针,唯一需要的指令仅仅是call,不生成负责计算的汇编指令,因此也就没有计算过程,只有实际用到汇编计算指令时才需要“先计算”过程。


    2019年4月19日 9:51

全部回复

  • 2019年4月17日 11:40
  • 按照这个规则可以解释通,但是这个是什么原理?我以为cout << parameter 会先结合,按照链式函数调用的规则逐步调用后面的,但为啥计算顺序是从右至左的呢?
    2019年4月17日 15:46
  • 如果在输出之前先进行了一轮运算,那么第一行为啥不是1:1而是1:2?
    2019年4月17日 16:05
  • 是先完成计算,再压栈,最后出栈显示。cout是后进先出结构。
    例如:cout << i++ << i << ++i;
    可以理解为:
    ++i;
    auto temp = i;
    ++i;
    cout << temp << i << i;
    因为后置的自增减运算符是有临时变量的。
    i++;这一句相当于下面这两行:
    auto temp = i;
    ++i;
    其实这个过程和函数实参的计算与压栈过程是完全一样的,先计算后压栈,从右向左进行。当然这个不在c/c++标准里,不同编译器的实现允许不同,因此c/c++程序员一般都遵从不在函数实参或cout<<这样的操作中进行有相互影响的运算这一准则。比如:
    int add(int a, int b, int c)
    {
        return a + b + c;
    }
    auto i = 0;
    调用add(i++, i, ++i);后得到的是5。
    2019年4月17日 16:55
  • 谢谢,解答的非常清楚,抱歉回复晚了。
    2019年4月18日 22:42
  • 我又屡了一下思路,发现还是有些不对,如果是先计算再压栈的话,可以理解第一行为什么会输出是1:2,可是如果我把第二行改成:

    cout << *intPtr(&intInit) << ":" << intInit<<endl;

    那么它的输出是4:2。按照上面的思路,先计算完再压栈,应该是

    intInit

    *intPtr(&intInit)

    计算完再压栈的话应该都是4:4啊,为啥是4:2,感觉这个通过指针获取变量内容和直接获取是不是哪里不一样?还是说就是边计算边压栈,那这样的话,第一行应该还是1:1啊。苦恼啊。。。


    2019年4月18日 23:04
  • 

    刚才查看了下我的编译器参数,没有启用优化。

    2019年4月19日 8:50
  • 	int intInit = 1;
    int * (*intPtr)(int *) = getInt2;
    cout << intInit++ << ":" << intInit << endl;
    cout << *intPtr(&intInit) << ":" << intInit<<endl;
    cout << intInit << endl;
    cout << *intPtr(&intInit) << endl;
    //(* ptr)函数书写
    cout << *(*intPtr)(&intInit) << ":" << *(*intPtr)(&intInit) << endl;
    cout << intInit << endl;

    运行结果为:

    1:2 4:4 4 8 16:32 32

    这是在c++ 17标准运行的结果,看来确实是内部机制哪里有问题,等有时间研究下c++ 17的标准。
    2019年4月19日 9:10
  • 为了简单起见,直接分析汇编码吧。

    	int intInit = 1;
    00E126B2  mov         dword ptr [intInit],1  
    	int * (*intPtr)(int *) = getInt2;
    00E126B9  mov         dword ptr [intPtr],offset getInt2 (0E11262h)  
    	cout << intInit++ << ":" << intInit << endl;
    00E126C0  mov         eax,dword ptr [intInit]  
    00E126C3  mov         dword ptr [ebp-0E0h],eax  
    00E126C9  mov         ecx,dword ptr [intInit]  
    00E126CC  add         ecx,1  
    00E126CF  mov         dword ptr [intInit],ecx  
    00E126D2  mov         esi,esp  
    00E126D4  push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    00E126D9  mov         edi,esp  
    00E126DB  mov         edx,dword ptr [intInit]  
    00E126DE  push        edx  
    00E126DF  push        offset string ":" (0E19B30h)  
    00E126E4  mov         ebx,esp  
    00E126E6  mov         eax,dword ptr [ebp-0E0h]  
    00E126EC  push        eax  
    00E126ED  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0E1D0DCh)]  
    00E126F3  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A4h)]  
    00E126F9  cmp         ebx,esp  
    00E126FB  call        __RTC_CheckEsp (0E1128Ah)  
    00E12700  push        eax  
    00E12701  call        std::operator<<<std::char_traits<char> > (0E1120Dh)  
    00E12706  add         esp,8  
    00E12709  mov         ecx,eax  
    00E1270B  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A4h)]  
    00E12711  cmp         edi,esp  
    00E12713  call        __RTC_CheckEsp (0E1128Ah)  
    00E12718  mov         ecx,eax  
    00E1271A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A8h)]  
    00E12720  cmp         esi,esp  
    00E12722  call        __RTC_CheckEsp (0E1128Ah)  
    	cout << intPtr(&intInit) << ":" << intInit << endl;
    00E12727  mov         esi,esp  
    00E12729  push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    00E1272E  mov         edi,esp  
    00E12730  mov         eax,dword ptr [intInit]  
    00E12733  push        eax  
    00E12734  push        offset string ":" (0E19B30h)  
    00E12739  mov         ebx,esp  
    00E1273B  lea         ecx,[intInit]  
    00E1273E  push        ecx  
    00E1273F  call        dword ptr [intPtr]  
    00E12742  add         esp,4  
    00E12745  cmp         ebx,esp  
    00E12747  call        __RTC_CheckEsp (0E1128Ah)  
    00E1274C  mov         ebx,esp  
    00E1274E  push        eax  
    00E1274F  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0E1D0DCh)]  
    00E12755  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A0h)]  
    00E1275B  cmp         ebx,esp  
    00E1275D  call        __RTC_CheckEsp (0E1128Ah)  
    00E12762  push        eax  
    00E12763  call        std::operator<<<std::char_traits<char> > (0E1120Dh)  
    00E12768  add         esp,8  
    00E1276B  mov         ecx,eax  
    00E1276D  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A4h)]  
    00E12773  cmp         edi,esp  
    00E12775  call        __RTC_CheckEsp (0E1128Ah)  
    00E1277A  mov         ecx,eax  
    00E1277C  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E1D0A8h)]  
    00E12782  cmp         esi,esp  
    00E12784  call        __RTC_CheckEsp (0E1128Ah)  


    cout << intInit++ << ":" << intInit << endl;和cout << intPtr(&intInit) << ":" << intInit << endl;这两句后面的输出流部分是一样的,所以咱们就看前面计算和压栈的这部分。

    	cout << intInit++ << ":" << intInit << endl;
    00E126C0  mov         eax,dword ptr [intInit]  
    00E126C3  mov         dword ptr [ebp-0E0h],eax  
    00E126C9  mov         ecx,dword ptr [intInit]  
    00E126CC  add         ecx,1  
    00E126CF  mov         dword ptr [intInit],ecx  
    00E126D2  mov         esi,esp  
    00E126D4  push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    00E126D9  mov         edi,esp  
    00E126DB  mov         edx,dword ptr [intInit]  
    00E126DE  push        edx  
    00E126DF  push        offset string ":" (0E19B30h)  
    00E126E4  mov         ebx,esp  
    00E126E6  mov         eax,dword ptr [ebp-0E0h]  
    	cout << intPtr(&intInit) << ":" << intInit << endl;
    00E12727  mov         esi,esp  
    00E12729  push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    00E1272E  mov         edi,esp  
    00E12730  mov         eax,dword ptr [intInit]  
    00E12733  push        eax  
    00E12734  push        offset string ":" (0E19B30h)  
    00E12739  mov         ebx,esp  
    00E1273B  lea         ecx,[intInit]  
    00E1273E  push        ecx  
    00E1273F  call        dword ptr [intPtr]  
    00E12742  add         esp,4  
    00E12745  cmp         ebx,esp  
    00E12747  call        __RTC_CheckEsp (0E1128Ah)  
    00E1274C  mov         ebx,esp  


    很显然在经过编译器优化之后,实际步骤和理论上的步骤不尽相同。cout << intInit++ << ":" << intInit << endl;这一部分是:

    mov         eax,dword ptr [intInit]
    mov         dword ptr [ebp-0E0h],eax  //这一句和上一句是将变量intInit赋值给匿名变量ebp-0E0h
    mov         ecx,dword ptr [intInit]  //变量intInit存入寄存器ecx
    add         ecx,1  //等于是++intInit
    mov         dword ptr [intInit],ecx  //ecx中的计算结果返回给变量intInit
    mov         esi,esp  
    push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  
    mov         edi,esp  
    mov         edx,dword ptr [intInit]  //将经过++后的intInit变量压栈
    push        edx  
    push        offset string ":" (0E19B30h)  
    mov         ebx,esp  
    mov         eax,dword ptr [ebp-0E0h]  //将匿名变量ebp-0E0h压栈

    因此上面的代码相当于:

    auto temp = intInit;
    ++intInit;
    cout << temp << ":" << intInit << endl;

    这个和之前的理论预测完全相同。

    接下来再看cout << intPtr(&intInit) << ":" << intInit << endl;这部分。

    mov         esi,esp  
    push        offset std::endl<char,std::char_traits<char> > (0E112ADh)  //直接开始压栈
    mov         edi,esp  
    mov         eax,dword ptr [intInit]  //将intInit变量压栈
    push        eax  
    push        offset string ":" (0E19B30h)  
    mov         ebx,esp  
    lea         ecx,[intInit]  
    push        ecx  //将intInit变量的地址压栈
    call        dword ptr [intPtr]  //通过函数指针调函数
    add         esp,4  
    cmp         ebx,esp  
    call        __RTC_CheckEsp (0E1128Ah)  
    mov         ebx,esp 

    这段代码相当于:

    cout << ":" << intInit << endl;
    intPtr(&intInit);
    cout << &intInit;

    为什么会这样呢?因为函数的返回值是一个值,不是计算语句,因此cout << intPtr(&intInit) << ":" << intInit << endl;这句话中实际上没有任何计算,所以直接就开始压栈了,没有计算过程,遇到了函数的时候就call一下,反正函数栈对当前栈没影响。如果你把前面cout << intPtr(&intInit) << ":" << intInit << endl;这句改换成cout << ++(*intPtr(&intInit)) << ":" << intInit << endl;再运行就会得到5:5了。所以结论就是函数是一个指针,唯一需要的指令仅仅是call,不生成负责计算的汇编指令,因此也就没有计算过程,只有实际用到汇编计算指令时才需要“先计算”过程。


    2019年4月19日 9:51
  • 总算把前面的问题整理完了,结论就是上面的最终回复。现在再来看看c++17:

    	cout << intInit++ << ":" << intInit << endl;
    003F22DC  mov         dword ptr [ebp-0E4h],eax  
    003F22E2  mov         eax,dword ptr [intInit]  
    003F22E5  mov         dword ptr [ebp-0E8h],eax  
    003F22EB  mov         esi,esp  
    003F22ED  mov         ecx,dword ptr [ebp-0E8h]  
    003F22F3  push        ecx  
    003F22F4  mov         ecx,dword ptr [ebp-0E4h]  
    003F22FA  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03FD0A4h)]  
    003F2300  cmp         esi,esp  
    003F2302  call        __RTC_CheckEsp (03F128Ah)  
    003F2307  mov         dword ptr [ebp-0ECh],eax  
    003F230D  mov         esi,esp  
    003F230F  push        offset std::endl<char,std::char_traits<char> > (03F12ADh)  
    003F2314  mov         ecx,dword ptr [ebp-0ECh]  
    003F231A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03FD0A8h)]  
    003F2320  cmp         esi,esp  
    003F2322  call        __RTC_CheckEsp (03F128Ah)  
    	cout << *intPtr(&intInit) << ":" << intInit << endl;
    003F2327  mov         eax,dword ptr [intPtr]  
    003F232A  mov         dword ptr [ebp-0E0h],eax  
    003F2330  mov         esi,esp  
    003F2332  lea         ecx,[intInit]  
    003F2335  push        ecx  
    003F2336  call        dword ptr [ebp-0E0h]  
    003F233C  add         esp,4  
    003F233F  cmp         esi,esp  
    003F2341  call        __RTC_CheckEsp (03F128Ah)  
    003F2346  mov         edx,dword ptr [eax]  
    003F2348  mov         dword ptr [ebp-0E4h],edx  
    003F234E  mov         esi,esp  
    003F2350  mov         eax,dword ptr [ebp-0E4h]  
    003F2356  push        eax  
    003F2357  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (03FD0DCh)]  
    003F235D  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03FD0A4h)]  
    003F2363  cmp         esi,esp  
    003F2365  call        __RTC_CheckEsp (03F128Ah)  
    003F236A  push        offset string ":" (03F9B30h)  
    003F236F  push        eax  
    003F2370  call        std::operator<<<std::char_traits<char> > (03F120Dh)  
    003F2375  add         esp,8  
    003F2378  mov         dword ptr [ebp-0E8h],eax  
    003F237E  mov         ecx,dword ptr [intInit]  
    003F2381  mov         dword ptr [ebp-0ECh],ecx  
    003F2387  mov         esi,esp  
    003F2389  mov         edx,dword ptr [ebp-0ECh]  
    003F238F  push        edx  
    003F2390  mov         ecx,dword ptr [ebp-0E8h]  
    003F2396  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03FD0A4h)]  
    003F239C  cmp         esi,esp  
    003F239E  call        __RTC_CheckEsp (03F128Ah)  
    003F23A3  mov         dword ptr [ebp-0F0h],eax  
    003F23A9  mov         esi,esp  
    003F23AB  push        offset std::endl<char,std::char_traits<char> > (03F12ADh)  
    003F23B0  mov         ecx,dword ptr [ebp-0F0h]  
    003F23B6  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (03FD0A8h)]  
    003F23BC  cmp         esi,esp  
    003F23BE  call        __RTC_CheckEsp (03F128Ah)  
    很显然c++17中call函数的这部分被挪到前面去了。

    2019年4月19日 10:12
  • 大哥很强,我回去好好复习下汇编,但是我知道这肯定是答案了。
    2019年4月19日 10:17
  • 看看release的汇编码吧。

    //c++14:cout << *intPtr(&intInit) << ":" << intInit << endl; 00F8108D push dword ptr [esp+4] 00F81091 lea eax,[esp+8] 00F81095 push eax 00F81096 call getInt2 (0F81030h) 00F8109B mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0F8307Ch)] 00F810A1 add esp,4 00F810A4 push eax 00F810A5 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0F83034h)] 00F810AB mov ecx,eax 00F810AD call std::operator<<<std::char_traits<char> > (0F81330h) 00F810B2 mov ecx,eax 00F810B4 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0F8303Ch)] 00F810BA mov ecx,eax 00F810BC call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0F83048h)]

    //c++17:cout << *intPtr(&intInit) << ":" << intInit << endl; 01191090 call getInt2 (01191030h) 01191095 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0119307Ch)] 0119109B add esp,4 0119109E push eax 0119109F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (01193034h)] 011910A5 mov ecx,eax 011910A7 call std::operator<<<std::char_traits<char> > (01191330h) 011910AC push dword ptr [esp+8] 011910B0 mov ecx,eax 011910B2 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0119303Ch)] 011910B8 push offset std::endl<char,std::char_traits<char> > (01191550h) 011910BD mov ecx,eax 011910BF call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (01193048h)]

    这个更清楚。c++14里面是先计算,然后依次压栈,然后call函数,最后再塞到stream里,这几步依次进行。c++17是先计算并call函数,然后直接往stream里面塞,每压一个数据就紧接着call一次<<运算符函数。这个和c++标准显然无关,c++标准里面没有规定编译器行为,显然是微软自己调整了编译顺序。

    2019年4月19日 10:32