#include <stdio.h>
int main(void)
{
int dd=8;
printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
return 0;
}
/*在VC++6.0编译运行输出结果为:
7 ;
7 ;
7 ;
7 ;
Press any key to continue
*/
/*
而完全一样的代码,在VS2005编译运行输出结果为:
7 ;
8 ;
7 ;
8 ;
请按任意键继续. . .
*/
疑问:
1、看结果应该是跟编译器有关,那么,两个编译器,对于这个printf函数的初始化列表里的dd++,dd--,dd++,--dd,是按照什么顺序把这四个参数压栈的?
2、为啥后置的自增、自减运算符,好像并不改变变量的值,这跟平时作为一般变量使用时不同么?
3、对于printf这个函数,参数列表的压栈和计算,是不是同步的?也就是说,假设从右往左的顺序压栈,先把 --dd 的结果入栈,还是只是把 --dd 这个变量以及操作符入栈?(可能这样问得有点白痴。。。计算机内存中存储的都是二进制数据。。。)如果是前者,那么参数的压栈以及计算应该是同步的,那么后置自增自减运算符应该有效啊。。。。;如果是后者,那么这个计算的操作,是在什么时候进行啊?
4、本人菜鸟,实在搞不明白这些,请大家不吝赐教,谢谢了!^_^
14 个解决方案
#1
哎,看的都头晕,这代码不能看
#2
我只知道dd++和dd--是先用后加或减,显示的还是变量加之前的值;而运算符在前面的是先加后用。
#3
gcc 下: 7 8 7 8
编译器问题,不用纠结,不要写出这样的代码。
编译器问题,不用纠结,不要写出这样的代码。
#4
纠结的原因就一个:自找麻烦。
你先算好了再printf不就啥纠结都没了吗?
你先算好了再printf不就啥纠结都没了吗?
#5
汇编查看,你就知道了
#6
说实话,这种跟编译器有关的在一行代码上面对同一个变量的多次++,--,最好想方设法分割到多行上去,
避免出这样那样的问题,代码也易读得多。
printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
避免出这样那样的问题,代码也易读得多。
printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
#7
1.cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,参数由右向左压入堆栈,调用者负责清理堆栈。由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个明确的参数确定下来,就可以使用不定参数,这也是C语言的一大特色。
2.对于d++,++d这样的东西,你需要注意的是这是一个表达式,取的是d++或者++d这个表达式的值,根据前后缀操作符的规定,表达式d++是先取d的值,然后d自增,++d是先自增,再取d的值。
3.C语言没有规定函数参数的求值顺序。求值顺序和压栈顺序没有关系。这是一个序列点的问题。这是一个日经贴,详细可以参考:
http://topic.csdn.net/u/20110826/09/601ebe9c-c2ae-4d63-a4e2-506c618bb654.html?59993
2.对于d++,++d这样的东西,你需要注意的是这是一个表达式,取的是d++或者++d这个表达式的值,根据前后缀操作符的规定,表达式d++是先取d的值,然后d自增,++d是先自增,再取d的值。
3.C语言没有规定函数参数的求值顺序。求值顺序和压栈顺序没有关系。这是一个序列点的问题。这是一个日经贴,详细可以参考:
http://topic.csdn.net/u/20110826/09/601ebe9c-c2ae-4d63-a4e2-506c618bb654.html?59993
#8
vc6.0 下的部分
9: int dd=8;
0040D768 C7 45 FC 08 00 00 00 mov dword ptr [ebp-4],8
10: printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
0040D76F 8B 45 FC mov eax,dword ptr [ebp-4]
0040D772 83 E8 01 sub eax,1
0040D775 89 45 FC mov dword ptr [ebp-4],eax
0040D778 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D77B 51 push ecx
0040D77C 8B 55 FC mov edx,dword ptr [ebp-4]
0040D77F 89 55 F8 mov dword ptr [ebp-8],edx
0040D782 8B 45 F8 mov eax,dword ptr [ebp-8]
0040D785 50 push eax
0040D786 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D789 89 4D F4 mov dword ptr [ebp-0Ch],ecx
0040D78C 8B 55 F4 mov edx,dword ptr [ebp-0Ch]
0040D78F 52 push edx
0040D790 8B 45 FC mov eax,dword ptr [ebp-4]
0040D793 89 45 F0 mov dword ptr [ebp-10h],eax
0040D796 8B 4D F0 mov ecx,dword ptr [ebp-10h]
0040D799 51 push ecx
0040D79A 68 BC 2F 42 00 push offset string " %d ;\n %d ;\n %d ;\n %d ;\n " (00422fbc)
0040D79F 8B 55 FC mov edx,dword ptr [ebp-4]
0040D7A2 83 C2 01 add edx,1
0040D7A5 89 55 FC mov dword ptr [ebp-4],edx
0040D7A8 8B 45 FC mov eax,dword ptr [ebp-4]
0040D7AB 83 E8 01 sub eax,1
0040D7AE 89 45 FC mov dword ptr [ebp-4],eax
0040D7B1 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D7B4 83 C1 01 add ecx,1
0040D7B7 89 4D FC mov dword ptr [ebp-4],ecx
0040D7BA E8 01 39 FF FF call printf (004010c0)
0040D7BF 83 C4 14 add esp,14h
11:
12: return 0;
0040D7C2 33 C0 xor eax,eax
14: }
可以看 红色部分,首先是 将dd减1,然后 压栈四次,然后 加1,减1,加1
9: int dd=8;
0040D768 C7 45 FC 08 00 00 00 mov dword ptr [ebp-4],8
10: printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
0040D76F 8B 45 FC mov eax,dword ptr [ebp-4]
0040D772 83 E8 01 sub eax,1
0040D775 89 45 FC mov dword ptr [ebp-4],eax
0040D778 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D77B 51 push ecx
0040D77C 8B 55 FC mov edx,dword ptr [ebp-4]
0040D77F 89 55 F8 mov dword ptr [ebp-8],edx
0040D782 8B 45 F8 mov eax,dword ptr [ebp-8]
0040D785 50 push eax
0040D786 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D789 89 4D F4 mov dword ptr [ebp-0Ch],ecx
0040D78C 8B 55 F4 mov edx,dword ptr [ebp-0Ch]
0040D78F 52 push edx
0040D790 8B 45 FC mov eax,dword ptr [ebp-4]
0040D793 89 45 F0 mov dword ptr [ebp-10h],eax
0040D796 8B 4D F0 mov ecx,dword ptr [ebp-10h]
0040D799 51 push ecx
0040D79A 68 BC 2F 42 00 push offset string " %d ;\n %d ;\n %d ;\n %d ;\n " (00422fbc)
0040D79F 8B 55 FC mov edx,dword ptr [ebp-4]
0040D7A2 83 C2 01 add edx,1
0040D7A5 89 55 FC mov dword ptr [ebp-4],edx
0040D7A8 8B 45 FC mov eax,dword ptr [ebp-4]
0040D7AB 83 E8 01 sub eax,1
0040D7AE 89 45 FC mov dword ptr [ebp-4],eax
0040D7B1 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D7B4 83 C1 01 add ecx,1
0040D7B7 89 4D FC mov dword ptr [ebp-4],ecx
0040D7BA E8 01 39 FF FF call printf (004010c0)
0040D7BF 83 C4 14 add esp,14h
11:
12: return 0;
0040D7C2 33 C0 xor eax,eax
14: }
可以看 红色部分,首先是 将dd减1,然后 压栈四次,然后 加1,减1,加1
#9
就算自己一时看懂了,也不能这么写啊。备不住以后又忘了呢,搞得自己都看不懂。何况别人
#10
和编译器有关,
没必要纠结,具体的可以通过反汇编查看得知
没必要纠结,具体的可以通过反汇编查看得知
#11
可以撕掉让你产生这样的想法的书.
学C语言完全可以不知道入栈顺序.
计算顺序与入栈顺序无关,见7楼链接.
学C语言完全可以不知道入栈顺序.
计算顺序与入栈顺序无关,见7楼链接.
#12
VC调试(TC或BC用TD调试)时按Alt+8、Alt+6和Alt+5,打开汇编窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应内存和寄存器变化,这样过一遍不就啥都明白了吗。
对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习汇编以及C和汇编的对应关系。
从汇编的角度理解和学习C语言的指针,原本看似复杂的东西就会变得非常简单!
指针即地址。“地址又是啥?”“只能从汇编语言和计算机组成原理的角度去解释了。”
提醒:
“学习用汇编语言写程序”
和
“VC调试(TC或BC用TD调试)时按Alt+8、Alt+6和Alt+5,打开汇编窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应内存和寄存器变化,这样过一遍不就啥都明白了吗。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习C和汇编的对应关系。”
不是一回事!
不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!
不要写连自己也预测不了结果的代码!
对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习汇编以及C和汇编的对应关系。
从汇编的角度理解和学习C语言的指针,原本看似复杂的东西就会变得非常简单!
指针即地址。“地址又是啥?”“只能从汇编语言和计算机组成原理的角度去解释了。”
提醒:
“学习用汇编语言写程序”
和
“VC调试(TC或BC用TD调试)时按Alt+8、Alt+6和Alt+5,打开汇编窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应内存和寄存器变化,这样过一遍不就啥都明白了吗。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习C和汇编的对应关系。”
不是一回事!
不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!
不要写连自己也预测不了结果的代码!
#13
压栈 从后往前
计算顺序未知
计算顺序未知
#14
参数的求值顺序和入栈顺序不是一码事~~是不同层面上的两个概念
#1
哎,看的都头晕,这代码不能看
#2
我只知道dd++和dd--是先用后加或减,显示的还是变量加之前的值;而运算符在前面的是先加后用。
#3
gcc 下: 7 8 7 8
编译器问题,不用纠结,不要写出这样的代码。
编译器问题,不用纠结,不要写出这样的代码。
#4
纠结的原因就一个:自找麻烦。
你先算好了再printf不就啥纠结都没了吗?
你先算好了再printf不就啥纠结都没了吗?
#5
汇编查看,你就知道了
#6
说实话,这种跟编译器有关的在一行代码上面对同一个变量的多次++,--,最好想方设法分割到多行上去,
避免出这样那样的问题,代码也易读得多。
printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
避免出这样那样的问题,代码也易读得多。
printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
#7
1.cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,参数由右向左压入堆栈,调用者负责清理堆栈。由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个明确的参数确定下来,就可以使用不定参数,这也是C语言的一大特色。
2.对于d++,++d这样的东西,你需要注意的是这是一个表达式,取的是d++或者++d这个表达式的值,根据前后缀操作符的规定,表达式d++是先取d的值,然后d自增,++d是先自增,再取d的值。
3.C语言没有规定函数参数的求值顺序。求值顺序和压栈顺序没有关系。这是一个序列点的问题。这是一个日经贴,详细可以参考:
http://topic.csdn.net/u/20110826/09/601ebe9c-c2ae-4d63-a4e2-506c618bb654.html?59993
2.对于d++,++d这样的东西,你需要注意的是这是一个表达式,取的是d++或者++d这个表达式的值,根据前后缀操作符的规定,表达式d++是先取d的值,然后d自增,++d是先自增,再取d的值。
3.C语言没有规定函数参数的求值顺序。求值顺序和压栈顺序没有关系。这是一个序列点的问题。这是一个日经贴,详细可以参考:
http://topic.csdn.net/u/20110826/09/601ebe9c-c2ae-4d63-a4e2-506c618bb654.html?59993
#8
vc6.0 下的部分
9: int dd=8;
0040D768 C7 45 FC 08 00 00 00 mov dword ptr [ebp-4],8
10: printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
0040D76F 8B 45 FC mov eax,dword ptr [ebp-4]
0040D772 83 E8 01 sub eax,1
0040D775 89 45 FC mov dword ptr [ebp-4],eax
0040D778 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D77B 51 push ecx
0040D77C 8B 55 FC mov edx,dword ptr [ebp-4]
0040D77F 89 55 F8 mov dword ptr [ebp-8],edx
0040D782 8B 45 F8 mov eax,dword ptr [ebp-8]
0040D785 50 push eax
0040D786 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D789 89 4D F4 mov dword ptr [ebp-0Ch],ecx
0040D78C 8B 55 F4 mov edx,dword ptr [ebp-0Ch]
0040D78F 52 push edx
0040D790 8B 45 FC mov eax,dword ptr [ebp-4]
0040D793 89 45 F0 mov dword ptr [ebp-10h],eax
0040D796 8B 4D F0 mov ecx,dword ptr [ebp-10h]
0040D799 51 push ecx
0040D79A 68 BC 2F 42 00 push offset string " %d ;\n %d ;\n %d ;\n %d ;\n " (00422fbc)
0040D79F 8B 55 FC mov edx,dword ptr [ebp-4]
0040D7A2 83 C2 01 add edx,1
0040D7A5 89 55 FC mov dword ptr [ebp-4],edx
0040D7A8 8B 45 FC mov eax,dword ptr [ebp-4]
0040D7AB 83 E8 01 sub eax,1
0040D7AE 89 45 FC mov dword ptr [ebp-4],eax
0040D7B1 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D7B4 83 C1 01 add ecx,1
0040D7B7 89 4D FC mov dword ptr [ebp-4],ecx
0040D7BA E8 01 39 FF FF call printf (004010c0)
0040D7BF 83 C4 14 add esp,14h
11:
12: return 0;
0040D7C2 33 C0 xor eax,eax
14: }
可以看 红色部分,首先是 将dd减1,然后 压栈四次,然后 加1,减1,加1
9: int dd=8;
0040D768 C7 45 FC 08 00 00 00 mov dword ptr [ebp-4],8
10: printf(" %d ;\n %d ;\n %d ;\n %d ;\n ",dd++,dd--,dd++,--dd);
0040D76F 8B 45 FC mov eax,dword ptr [ebp-4]
0040D772 83 E8 01 sub eax,1
0040D775 89 45 FC mov dword ptr [ebp-4],eax
0040D778 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D77B 51 push ecx
0040D77C 8B 55 FC mov edx,dword ptr [ebp-4]
0040D77F 89 55 F8 mov dword ptr [ebp-8],edx
0040D782 8B 45 F8 mov eax,dword ptr [ebp-8]
0040D785 50 push eax
0040D786 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D789 89 4D F4 mov dword ptr [ebp-0Ch],ecx
0040D78C 8B 55 F4 mov edx,dword ptr [ebp-0Ch]
0040D78F 52 push edx
0040D790 8B 45 FC mov eax,dword ptr [ebp-4]
0040D793 89 45 F0 mov dword ptr [ebp-10h],eax
0040D796 8B 4D F0 mov ecx,dword ptr [ebp-10h]
0040D799 51 push ecx
0040D79A 68 BC 2F 42 00 push offset string " %d ;\n %d ;\n %d ;\n %d ;\n " (00422fbc)
0040D79F 8B 55 FC mov edx,dword ptr [ebp-4]
0040D7A2 83 C2 01 add edx,1
0040D7A5 89 55 FC mov dword ptr [ebp-4],edx
0040D7A8 8B 45 FC mov eax,dword ptr [ebp-4]
0040D7AB 83 E8 01 sub eax,1
0040D7AE 89 45 FC mov dword ptr [ebp-4],eax
0040D7B1 8B 4D FC mov ecx,dword ptr [ebp-4]
0040D7B4 83 C1 01 add ecx,1
0040D7B7 89 4D FC mov dword ptr [ebp-4],ecx
0040D7BA E8 01 39 FF FF call printf (004010c0)
0040D7BF 83 C4 14 add esp,14h
11:
12: return 0;
0040D7C2 33 C0 xor eax,eax
14: }
可以看 红色部分,首先是 将dd减1,然后 压栈四次,然后 加1,减1,加1
#9
就算自己一时看懂了,也不能这么写啊。备不住以后又忘了呢,搞得自己都看不懂。何况别人
#10
和编译器有关,
没必要纠结,具体的可以通过反汇编查看得知
没必要纠结,具体的可以通过反汇编查看得知
#11
可以撕掉让你产生这样的想法的书.
学C语言完全可以不知道入栈顺序.
计算顺序与入栈顺序无关,见7楼链接.
学C语言完全可以不知道入栈顺序.
计算顺序与入栈顺序无关,见7楼链接.
#12
VC调试(TC或BC用TD调试)时按Alt+8、Alt+6和Alt+5,打开汇编窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应内存和寄存器变化,这样过一遍不就啥都明白了吗。
对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习汇编以及C和汇编的对应关系。
从汇编的角度理解和学习C语言的指针,原本看似复杂的东西就会变得非常简单!
指针即地址。“地址又是啥?”“只能从汇编语言和计算机组成原理的角度去解释了。”
提醒:
“学习用汇编语言写程序”
和
“VC调试(TC或BC用TD调试)时按Alt+8、Alt+6和Alt+5,打开汇编窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应内存和寄存器变化,这样过一遍不就啥都明白了吗。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习C和汇编的对应关系。”
不是一回事!
不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!
不要写连自己也预测不了结果的代码!
对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习汇编以及C和汇编的对应关系。
从汇编的角度理解和学习C语言的指针,原本看似复杂的东西就会变得非常简单!
指针即地址。“地址又是啥?”“只能从汇编语言和计算机组成原理的角度去解释了。”
提醒:
“学习用汇编语言写程序”
和
“VC调试(TC或BC用TD调试)时按Alt+8、Alt+6和Alt+5,打开汇编窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应内存和寄存器变化,这样过一遍不就啥都明白了吗。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习C和汇编的对应关系。”
不是一回事!
不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!
不要写连自己也预测不了结果的代码!
#13
压栈 从后往前
计算顺序未知
计算顺序未知
#14
参数的求值顺序和入栈顺序不是一码事~~是不同层面上的两个概念