インクリメントはいつ行われる?

昨日のよくわからない問題ですが、以下のような簡単なチェックをしてみたところ挙動がわかりました。

int j=0;
printf("j = %d, j++ = %d\n",j,j++);
//Releaseビルド(最適化:実行速度)だと
出力:j = 0, j++ = 0

混合モード
push        0    
push        0    
push        offset string "j = %d, j++ = %d\n" (413490h) 
call        printf (40B5D4h) 
//Releaseビルド(最適化:無効)
出力:j = 0, j++ = 0

混合モード
mov         dword ptr [j],0 
mov         eax,dword ptr [j] 
mov         dword ptr [ebp-5Ch],eax 
mov         ecx,dword ptr [ebp-5Ch] 
push        ecx  
mov         edx,dword ptr [j] 
push        edx  
push        offset string "j = %d, j++ = %d\n" (4154A8h) 
call        printf (40D515h) 
add         esp,0Ch 
mov         eax,dword ptr [j] 
add         eax,1 
mov         dword ptr [j],eax 
//Debugビルドだと
出力:j = 1, j++ = 0

混合モード
mov         eax,dword ptr [j] 
mov         dword ptr [ebp-15Ch],eax 
mov         ecx,dword ptr [j] 
add         ecx,1 
mov         dword ptr [j],ecx 
mov         edx,dword ptr [ebp-15Ch] 
push        edx  
mov         eax,dword ptr [j] 
push        eax  
push        offset string "j = %d, j++ = %d\n" (45E404h) 
call        @ILT+5575(_printf) (41F5CCh) 
add         esp,0Ch 
ちなみにこうするとDebugモードでは
printf("j++ = %d, j = %d\n",j++,j);
出力:j++ = 0, j = 1


ふむふむ。混合モードなんて普段は見向きもしませんが、一つの文だけをアセンブラで見てみればそんなに難しいものではないんですね。なかなか面白いです。DebugとReleaseで違うのは最適化のせいかなとも思いましたが、Releaseビルドの最適化を無効にしても出力は変わりませんでした。


この中では最適化無効時のビルドが一番納得できる形ですけど、同じ式の中に同じ変数を入れてインクリメントするときの順番ってのは決まってないのかな。