给定堆栈和寄存器的状态,我们可以预测printf的未定义行为的结果
以下是类测验的一些简单C代码:
#include int main() { float a = 2.3; printf("%d\n", a); return 0; }
编译并运行:
Apple LLVM版本6.1.0( clang-602.0.53
)(基于LLVM 3.6.0svn)
目标: x86_64
-apple-darwin14.5.0
此代码的输出undefined
。 我试图通过检查调试器附近的内存(gdb中的X
命令)来预测输出。 例如,当a的地址是0x7fff5fbffb98
, &a
附近的上下文如下:
0x7fff5fbffb98: 1075000115 0x7fff5fbffb9c: 0 0x7fff5fbffba0: 1606417336 0x7fff5fbffba4: 32767 0x7fff5fbffba8: -1754266167 0x7fff5fbffbac: 32767 0x7fff5fbffbb0: -1754266167 0x7fff5fbffbb4: 32767
然后printf
的输出是1606417352
。 我知道使用不正确的说明符时的输出是未定义的。 出于好奇,我预计这个未定义行为的输出与正在运行的堆栈或寄存器的某些内存有关,但我还没弄清楚如何关联它。
那么使用哪个地址或寄存器来设置这个printf
的输出? 换句话说,给定运行堆栈的状态以及来自所有寄存器的所有值,我们可以预测(如果是这样)这种未定义行为的输出吗?
在具有SysV调用约定的 AMD64上(几乎每个系统都使用Windows),函数的前几个参数在寄存器中传递。 这就是为什么你没有在堆栈上看到它们:它们不会在堆栈上传递。
具体来说,前几个整数或指针参数在rdi
, rsi
, rdx
中传递,而前几个浮点参数在xmm0
, xmm1
和xmm2
中传递。 由于a
在xmm0
中传递但printf
尝试从rsi
读取数字,因此您不会看到所提供的数字与打印出的数字之间存在任何关联。
对于未来的读者:请注意OP尝试做的是未定义的行为 。 ISO 9899:2011规定应该为%d
传递一个int
,但是OP试图将它与double
一起使用(在默认参数提升之后)。 为此,OP应该使用%f
代替。 使用错误的格式说明符是未定义的行为。 请不要认为观察OP会在您的系统或任何地方保留,也不要写这种代码。
您尝试将%d
用于float
:
d
说明符用于带符号的十进制整数
f
说明符用于十进制浮点
使用错误的说明符会导致未定义的行为
您依赖于自动变量的地址:
我尝试通过查看附近的内存来预测输出
a
是一个自动变量,每次编译代码时它的地址都会改变,因此每次编译代码时,memory-near-a也会改变。
因此,“查看附近的内存”也会导致未定义的行为。
解:
你与未定义的行为(在这种情况下)无关,所以只是忘记它以节省时间,它会让你的生活更轻松。