使用 sprintf / printf 使用 %ld 格式字符串而不是 %d 和 int 数据类型的效果

effect of using sprintf / printf using %ld format string instead of %d with int data type

我们有一些遗留代码,在某个时间点 long 数据类型被重构为 int 数据类型。在此重构期间,许多 printf / sprintf 格式语句被错误地保留为 %ld 而不是更改为 %d。例如:

int iExample = 32;
char buf[200];

sprintf(buf, "Example: %ld", iExample);

此代码在 GCC 和 VS2012 编译器上编译。我们使用 Coverity 进行静态代码分析,示例中的代码被标记为 'Printf arg type mismatch',严重程度为中等,CWE-686: Function Call With Incorrect Argument Type 我可以看到,如果格式字符串是一个有符号的 (%d) 有一个 unsigned int 类型或类似的东西。

我知道 sprintf 等的“_s”版本更安全,上面的代码也可以重构为使用 std::stringstream 等。但是它是遗留代码...

我同意上面的代码确实应该至少使用 %d 或重构为使用 std::stringstream 之类的东西。

出于好奇,上面的代码是否会产生不正确的结果?由于此遗留代码已经存在了相当长的一段时间,并且 似乎 工作正常。

已更新

它是未定义的,取决于实现。在 int 和 long 具有相同大小的实现中,它可能会按预期工作。但是只需在任何具有 32 位 int 和 64 位 long 的系统上尝试它,特别是如果您的整数不是最后一个格式参数,并且您可能会遇到问题,其中 printf 读取 64 位而只提供了 32 位,其余的很可能是垃圾,并且可能根据对齐方式,以下参数也无法正确访问。

就标准而言,行为是未定义的,这意味着该标准完全没有说明将会发生什么。

在实践中,如果 intlong 具有相同的大小和表示,它很可能 "work",即表现得好像使用了正确的格式字符串。 (在 32 位系统上 intlong 都是 32 位是很常见的)。

如果 longint 宽,它仍然可以工作 "correctly"。例如,调用约定可能是两种类型都在相同的寄存器中传递,或者两者都作为相同大小的机器 "words" 被压入堆栈。

或者它可能以任意糟糕的方式失败。如果 int 是 32 位而 long 是 64 位,则 printf 中尝试读取 long 对象的代码可能会得到一个由 32 位组成的 64 位对象实际传递的 int 与 32 位垃圾相结合。或者额外的 32 位可能始终为零,但 32 位有效位位于 64 位对象的错误末端。也可以想象,当只传递 32 位时获取 64 位可能会导致 other 参数出现问题;您可能会得到 iExample 的正确值,但可能会从错误的堆栈偏移量中获取以下参数。

我的建议:应该修复代码以使用正确的格式字符串(并且你有工具来检测有问题的调用),但也做一些测试(在你关心的所有 C 实现上)看看是否它在实践中会引起任何可见的症状。测试的结果应该来决定修复问题的优先级,而不是决定是否修复问题。如果代码现在明显失败,您应该立即修复它。如果没有,您可以等到晚些时候(大概您还有其他事情要做)。