【问题标题】:Why can I printf with the wrong specifier and still get output?为什么我可以使用错误的说明符 printf 并仍然得到输出?
【发布时间】:2020-03-15 00:03:59
【问题描述】:

我的问题涉及 C printf() 函数背后的内存布局和机制。假设我有以下代码:

#include <stdio.h>

int main()
{
    short m_short;
    int m_int;

    m_int = -5339876;

    m_short = m_int;
    printf("%x\n", m_int);
    printf("%x\n", m_short);

    return 0;
}

在 GCC 7.5.0 上,此程序输出:

ffae851c
ffff851c

我的问题是,第二个十六进制数字中的ffff 实际上来自哪里?如果我是正确的,那些 fs 应该在 short 的范围之外,但是 printf 正在从某个地方获取它们。

当我使用说明符 %hx 正确格式化时,输出正确:

ffae851c
851c

据我研究,编译器只是截断数字的上半部分,如第二个输出所示。那么在第一个输出中,程序中的前四个fs 是否真的读入了它不应该读入的内存?还是 C 编译器在幕后仍然保留一个完整的整数,即使是一个短的、符号扩展的,但如果使用高半部分应该是未定义的行为?

注意:我正在进行研究,在实际应用程序中,我绝不会尝试滥用语言。

【问题讨论】:

    标签: c memory formatting printf


    【解决方案1】:

    当char 或short(包括有符号和无符号版本)用作没有特定类型的函数参数时(如... 的参数printf(format,...))1 sup>,它会自动提升为int(假设它还没有int那么宽2)。

    所以printf("%x\n", m_short); 有一个int 参数。这个论点的价值是什么?在赋值 m_short = m_int; 中,您尝试为其赋值 -5339876(用字节 0xffae851c 表示)。但是,-5339876 不适合这个 16 位短。在赋值中,会自动执行转换,并且当整数到有符号整数类型的转换不合适时,结果由实现定义。看起来你的实现,和很多人一样,使用二进制补码并且只取整数的低位。因此,它将字节 0x851c 放入 m_short,表示值 -31460。

    回想一下,它被提升回int 以用作printf 的参数。在这种情况下,它适合 int,所以结果仍然是 -31460。在二进制补码int 中,用字节 0xffff851c 表示。

    现在我们知道传递给printf的是什么:一个int,字节0xffff851c代表值-31460。但是,您使用%x 打印它,它应该收到unsigned int。由于这种不匹配,行为不是由 C 标准定义的。然而,这是一个相对较小的不匹配,许多 C 实现让它滑动。 (即使使用-Wall,GCC 和 Clang 也不会发出警告。)

    假设您的 C 实现不将 printf 视为一个特殊的已知函数,而只是在您编写它时为调用生成代码,并且您稍后将该程序与 C 库链接。在这种情况下,编译器必须根据您平台的应用程序二进制接口 (ABI) 规范传递参数。 (除其他外,ABI 指定如何将参数传递给函数。)为了符合 ABI,C 编译器会将格式字符串的地址放在一个位置,将 int 的位放在另一个位置,然后它将调用printf。

    printf 例程将读取格式字符串,参见%x,并查找相应的参数,它应该是unsigned int。在我所知道的每个 C 实现和 ABI 中,int 和 unsigned int 在在同一个地方传递。它可能是处理器寄存器或堆栈上的位置。假设它在寄存器 r13 中。因此,编译器设计了您的调用例程,将带有字节 0xffff851c 的 int 放入 r13 中,printf 例程在 r13 中查找 unsigned int 并找到字节 0xffff851c。

    所以结果是printf 将字节 0xffff851c 解释为好像它们是 unsigned int,将它们格式化为 %x,并打印“ffff851c”。

    基本上,你侥幸逃脱,因为 (a) short 被提升为 int,其大小与 printf 所期望的 unsigned int 相同,以及 (b) 大多数 C 实现对于与printf 相同宽度的整数类型不匹配并不严格。如果您尝试使用%ld 打印int,您可能会得到不同的结果,例如打印值的高位中的“垃圾”位。或者您可能会遇到这样一种情况,您传递的参数应该与预期的参数printf 完全不同,因此没有一个位是正确的。在某些架构中,错误地传递参数可能会破坏堆栈并以各种方式破坏程序。

    脚注

    1这种自动提升也发生在许多其他表达方式中。

    2关于这些自动整数提升的一些技术细节目前不需要我们关注。

    【讨论】:

    • 埃里克,非常感谢!我一直在阅读“软件安全评估的艺术”一文,这帮助我连接了一些点。我忘记了整数提升发生在函数调用中,在这种情况下包括printf。如果我错了,请纠正我,但发生这种情况是因为转换回 short 将在 return 上进行,例如,如果 printf 是一个将值作为 short 返回的函数,但在这种情况下,@987654366 @ 在它仍然是 int 时打印它,或者不知道它是一个短隐式......
    猜你喜欢
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    • 2017-02-27
    • 2012-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多