如下:为什么输出的会是9和10呢,我觉得应该是8和9;还有,第一次执行my(4),count被初始化了,再执行my(5),count还会初始化吗 #include<stdio.h> |
|
这个和printf函数参数入栈的顺序有关,参数入栈的顺序是从右至左的,所以先运行的my(5),再运行的my(4),因此count被初始化为了5,故输出9和10。
|
|
5分 |
静态变量只初始化一次,,printf先运行my(5)之后就已经初始化count为5,再运行my(4)时,并不初始化count,仍然为5
|
5分 |
解释是错的 |
代码是错的
未定义行为 结果没有意义 楼主去看置顶帖吧 |
|
我的代码运行是没有问题的,我是调试的时候看的反汇编,确实是先将5入栈,然后运行my函数,再将4入栈,再次运行my,那么请问,你认为my函数是在什么时候运行的? |
|
这里,会先将5入栈,然后运行my函数,然后将my函数的返回值入栈,接下来调用my(4),最后将字符串入栈,然后调用printf,printf的调用约定是__cdecl,函数调用堆栈的操作是调用者处理的,所以printf支持可变参数列表。以下是反汇编代码 printf("%d\n%d\n",my(4),my(5)); 0041142E push 5 00411430 call my (411091h) 00411435 add esp,4 00411438 mov esi,esp 0041143A push eax 0041143B push 4 0041143D call my (411091h) 00411442 add esp,4 00411445 push eax 00411446 push offset string "%d\n%d\n" (41573Ch) 0041144B call dword ptr [__imp__printf (4182BCh)] 00411451 add esp,0Ch 00411454 cmp esi,esp 00411456 call @ILT+325(__RTC_CheckEsp) (41114Ah) |
|
10分 |
你没看懂我说什么 裘宗燕:C/C++ 语言中的表达式求值 经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?” 最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5: 要弄清这些,需要理解的一个问题是:如果程序里某处修改了一个变量(通过赋值、增量/减量操作等),什么时候从该变量能够取到新值?有人可能说,“这算什么问题!我修改了变量,再从这个变量取值,取到的当然是修改后的值!”其实事情并不这么简单。 C/C++ 语言是“基于表达式的语言”,所有计算(包括赋值)都在表达式里完成。“x = 1;”就是表达式“x = 1”后加表示语句结束的分号。要弄清程序的意义,首先要理解表达式的意义,也就是: 现在问题变成:如果C/C++ 程序里的某个表达式(部分)有副作用,这种副作用何时才能实际体现到使用中? 为使问题更清楚,我们假定程序里有代码片段“…a[i]++ … a[j] …”,假定当时i与j的值恰好相等(a[i] 和a[j] 正好引用同一数组元素);假定a[i]++ 确实在a[j] 之前计算;再假定其间没有其他修改a[i] 的动作。在这些假定下,a[i]++ 对 a[i] 的修改能反映到 a[j] 的求值中吗?注意:由于 i 与 j 相等的问题无法静态判定,在目标代码里,这两个数组元素访问(对内存的访问)必然通过两段独立代码完成。现代计算机的计算都在寄存器里做,问题现在变成:在取 a[j] 值的代码执行之前,a[i] 更新的值是否已经被(从寄存器)保存到内存?如果了解语言在这方面的规定,这个问题的答案就清楚了。 程序语言通常都规定了执行中变量修改的最晚实现时刻(称为顺序点、序点或执行点)。程序执行中存在一系列顺序点(时刻),语言保证一旦执行到达一个顺序点,在此之前发生的所有修改(副作用)都必须实现(必须反应到随后对同一存储位置的访问中),在此之后的所有修改都还没有发生。在顺序点之间则没有任何保证。对C/C++ 语言这类允许表达式有副作用的语言,顺序点的概念特别重要。 现在上面问题的回答已经很清楚了:如果在a[i]++ 和a[j] 之间存在一个顺序点,那么就能保证a[j] 将取得修改之后的值;否则就不能保证。 C/C++语言定义(语言的参考手册)明确定义了顺序点的概念。顺序点位于: 前面讨论中假定了a[i]++ 在a[i] 之前做。在一个程序片段里a[i]++ 究竟是否先做,还与它所在的表达式确定的计算过程有关。我们都熟悉C/C++ 语言有关优先级、结合性和括号的规定,而出现多个运算对象时的计算顺序却常常被人们忽略。 看下面例子:(a + b) * (c + d) fun(a++, b, a+5) 不少书籍在这些问题上有错(包括一些很流行的书)。例如说C/C++ 先算左边(或右边),或者说某个C/C++ 系统先计算某一边。这些说法都是错误的!一个C/C++ 系统可以永远先算左边或永远先算右边,也可以有时先算左边有时先算右边,或在同一表达式里有时先算左边有时先算右边。不同系统可能采用不同的顺序(因为都符合语言标准);同一系统的不同版本完全可以采用不同方式;同一版本在不同优化方式下,在不同位置都可能采用不同顺序。因为这些做法都符合语言规范。在这里还要注意顺序点的问题:即使某一边的表达式先算了,其副作用也可能没有反映到内存,因此对另一边的计算没有影响。 回到前面的例子:“谁知道下面C语句给n赋什么值?” 有人可能说,为什么人们设计 C/C++时不把顺序规定清楚,免去这些麻烦? C/C++ 语言的做法完全是有意而为,其目的就是允许编译器采用任何求值顺序,使编译器在优化中可以根据需要调整实现表达式求值的指令序列,以得到效率更高的代码。像Java那样严格规定表达式的求值顺序和效果,不仅限制了语言的实现方式,还要求更频繁的内存访问(以实现副作用),这些可能带来可观的效率损失。应该说,在这个问题上,C/C++和Java的选择都贯彻了它们各自的设计原则,各有所获(C/C++ 潜在的效率,Java更清晰的程序行为),当然也都有所失。还应该指出,大部分程序设计语言实际上都采用了类似C/C++的规定。 讨论了这么多,应该得到什么结论呢?C/C++ 语言的规定告诉我们,任何依赖于特定计算顺序、依赖于在顺序点之间实现修改效果的表达式,其结果都没有保证。程序设计中应该贯彻的规则是:如果在任何“完整表达式”(形成一段由顺序点结束的计算)里存在对同一“变量”的多个引用,那么表达式里就不应该出现对这一“变量”的副作用。否则就不能保证得到预期结果。注意:这里的问题不是在某个系统里试一试的问题,因为我们不可能试验所有可能的表达式组合形式以及所有可能的上下文。这里讨论的是语言,而不是某个实现。总而言之,绝不要写这种表达式,否则我们或早或晚会某种环境中遇到麻烦。 后记:去年参加一个学术会议,看到有同行写文章讨论某个C系统里表达式究竟按什么顺序求值,并总结出一些“规律”。从讨论中了解到某“程序员水平考试”出了这类题目。这使我感到很不安。今年给一个教师学习班讲课,发现许多专业课教师也对这一基本问题也不甚明了,更觉得问题确实严重。因此整理出这篇短文供大家参考。 后后记:4年多过去了,许多新的和老的教科书仍然在不厌其烦地讨论在C语言里原本并无意义的问题(如本文所指出的)。希望学习和使用C语言的人不要陷入其中。 |
汪国真刚逝世了,留下了一些诗篇;
谭浩强逝世的时候,不知道能不能将++,–带进坟墓。 |
|
哪个调用先入栈本来就是随机的。也只有小学生才会在printf里使用函数调用。完全不了解底层的工作机制。
|