i++和++i在函数入参时的一些问题
摘要
本文探讨了C++中i++和++i在函数参数传递时的行为差异及其背后的机制。作者通过两个问题详细分析了参数计算顺序和自增操作的执行时机。首先,C++标准规定函数参数的入参顺序为从右往左,但参数的计算顺序未被明确规定,因此不同编译器(如gcc和clang)可能采用不同的计算顺序,导致相同代码在不同编译器下的输出结果不同。其次,作者通过类的示例和汇编代码分析,解释了i++和++i的计算时机:i++会先将当前值传递给参数,再执行自增操作,而++i则在传递参数前完成自增操作。文章通过汇编代码验证了这一行为,清晰地展示了寄存器的操作过程。本文对C++开发者理解编译器行为和避免潜在问题具有重要参考价值。
问题一: test(x++, x == 1)
以下函数的输出是什么? 本文源码参考: https://gcc.godbolt.org/z/veMohGYob
| |
参数计算顺序和入参顺序
首先, 毫无疑问, C/C++标准规定的函数参数入参顺序是从右往左.
按照上述说法, 问题一的数输出应该就是0 0. 使用gcc编译, 发现输出结果确实是0 0, 但是使用clang编译的输出结果是0 1.
不同编译器编译同一个函数, 居然会导致函数的输出不一样. 这是因为在函数参数入参之前还有参数的计算过程.
在《UCB CS61a SICP Python 中文》一周目笔记(一)中提到了函数参数的应用序和正则序, 按照我们的认知和经验, C/C++编译器使用的是应用序, 所以在函数入参之前需要计算参数的结果. 但是, 同入参顺序, 参数计算也有顺序. C/C++标准中没有规定函数参数的计算顺序, 所以导致不同的编译器可能定义了不同的参数计算顺序.
如上结果:
- gcc的参数计算顺序是从右往左;
- clang的参数计算顺序是从左往右;
问题二: test(m_idx++)
下面这个类Test, 调用test()方法的输出是什么?
| |
这里不涉及问题一的计算顺序了. 经过测试, test的输出结果是0 1, 这意为着m_idx++在入参时是传入的没有++的m_idx, 但是在调用函数之后, 有发现m_idx被++了, 也就是说, m_idx + 1的动作发生在入参前后. 这里涉及到的知识点是x++和++x的计算时机.
i++和++i的计算时机
先来看看汇编结果:
| |
首先是拿到了m_idx, 然后将m_idx的值存入esi寄存器, 再将esi寄存器的值(这时候就是m_idx的值)存入eax寄存器, 然后eax+1, 再将eax寄存器加完后的结果存如m_idx, 然后调用test(int)函数. 在test(int)中会将esi寄存器的值作为入参参数.
所以我们可以得到一个结论: 执行test(m_idx++);时, 先将m_idx送入esi寄存器(源变址寄存器), 再复制到eax寄存器(累加寄存器), 再在eax寄存器执行++操作, 然后送入m_idx, 这时候m_idx已经执行完+1操作了. 参数入参时使用的是esi中的值, 所以入参的值是没有加一的值.
如果是test(++m_idx);呢?
| |
可以发现, 没有使用临时寄存器了, 而是直接在esi寄存器上使用+1操作. 所以入参的值就是加完之后的值.