求助各位高手,关于printf函数参数压栈和计算的顺序问题、、、、纠结了很久

时间:2023-01-09 22:33:21


#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不就啥纠结都没了吗?

#5


汇编查看,你就知道了

#6


说实话,这种跟编译器有关的在一行代码上面对同一个变量的多次++,--,最好想方设法分割到多行上去,
避免出这样那样的问题,代码也易读得多。

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

#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


就算自己一时看懂了,也不能这么写啊。备不住以后又忘了呢,搞得自己都看不懂。何况别人

#10


和编译器有关,
没必要纠结,具体的可以通过反汇编查看得知

#11


可以撕掉让你产生这样的想法的书.

学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、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!

不要写连自己也预测不了结果的代码!

#13


压栈 从后往前
计算顺序未知

#14


参数的求值顺序和入栈顺序不是一码事~~是不同层面上的两个概念

#1


哎,看的都头晕,这代码不能看

#2


我只知道dd++和dd--是先用后加或减,显示的还是变量加之前的值;而运算符在前面的是先加后用。

#3


gcc 下: 7 8 7 8

编译器问题,不用纠结,不要写出这样的代码。

#4


纠结的原因就一个:自找麻烦。

你先算好了再printf不就啥纠结都没了吗?

#5


汇编查看,你就知道了

#6


说实话,这种跟编译器有关的在一行代码上面对同一个变量的多次++,--,最好想方设法分割到多行上去,
避免出这样那样的问题,代码也易读得多。

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

#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


就算自己一时看懂了,也不能这么写啊。备不住以后又忘了呢,搞得自己都看不懂。何况别人

#10


和编译器有关,
没必要纠结,具体的可以通过反汇编查看得知

#11


可以撕掉让你产生这样的想法的书.

学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、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!

不要写连自己也预测不了结果的代码!

#13


压栈 从后往前
计算顺序未知

#14


参数的求值顺序和入栈顺序不是一码事~~是不同层面上的两个概念