【问题标题】:Is this DWORD related code undefined behaviour?这是与 DWORD 相关的代码未定义行为吗?
【发布时间】:2015-11-12 14:30:29
【问题描述】:

这是我第三次尝试澄清我对这个话题的困惑。但这次我有不同的问题。

我有这个代码

  DWORD v1, v2, v3, Build;
  GetVersion(&v1, &v2, &v3, &Build);
  sprintf(VersionStr, "%d.%d.%d.%d", v1, v2, v3, Build);

这可能是 10 年前使用 Visual Studio 编写的。我知道DWORD 总是unsigned——这是真的吗?

现在,here,其中一个答案引用了某些版本的标准(这个标准版本是否适用于我的代码?)其中提到了va_arg

在这一点上,标准并不是 100% 明确的。一方面,你得到 va_arg 的规范,其中 (§7.15.1.1/2):

如果没有实际的下一个参数,或者类型不兼容 实际下一个参数的类型(根据 默认参数提升),行为未定义,除了 以下情况:

一种类型是有符号整数类型,另一种类型是 对应的无符号整数类型,,其值可以表示为 两种类型;

一种是指向void的指针,另一种是指向字符的指针 输入。

另一方面,这个答案也提到了printf

另一方面,您会得到 printf 的规范(第 7.19.6.1/9 节):

如果任何参数不是对应的正确类型 转换规范,行为未定义。”

所以他首先引用了关于va_arg 的引用,然后引用了关于printf 的引用。他似乎也不清楚。另一个答案提到这违反了printf 合同,尽管有va_arg 文档。请看线程。我很困惑。


所以我的问题基本上是我在任何情况下都呈现未定义行为的代码?或者例如对于可以用int 表示的v1 的值,这不是未定义的行为(如我引用的答案之一所述)?

也可能是因为我的代码很旧,可能是旧版本的标准不是未定义的行为?

【问题讨论】:

  • 提出这个问题没有错。加一。但是,您的程序的行为是未定义的,这是无法回避的事实。
  • @Bathsheba:我明白了,但我也发布了指向其他似乎声称这是安全的答案的链接
  • 仔细阅读该标准会发现它并不安全。你真的应该在你的开发流程中安排一个修复。
  • @Bathsheba:另外,标准是否适用于我的代码,我的意思是如果我的代码是旧的,并且可能最近在标准中引入了 UB?
  • 遗憾的是:即使在我的 C 大学时代(1990 年代),不匹配的 printf 参数也是未定义的。

标签: c++ undefined-behavior


【解决方案1】:

这真的很简单:sprintf 中的格式说明符必须与传递的参数类型匹配。请不要试图推测此规则的例外情况。

由于 DWORD 是 unsigned long%d 不是unsigned long 的正确格式说明符,因此程序的行为是未定义的。您必须使用%lu。由于DWORD 不是标准类型,您应该包含一行

static_assert(std::is_same<DWORD, unsigned long>::value, "DWORD is not an unsigned long");

在你的程序中的某个地方。

【讨论】:

  • 我认为 DWORD 保证是 32 位无符号整数,因此转换为 std::uint32_t 而不是使用 unsigned long 可能更好。
  • 我的 MSVC 平台有“typedef unsigned long DWORD;”
  • 是的,因为unsigned long 在 MSVC 中正好是 32 位。在 GCC 或任何其他编译器中不需要如此。
  • 不,不,不。大小无关紧要。 %d 用于int%lu 用于unsigned long。即使将%d 用于long 也是UB! long 可能是 1 的补码,int 可能是 2 的补码,但 sizeof 可能是相同的!不要做这些假设。只需修复代码。
  • 我会使用 std::stringstream&lt;&lt; 而不是乱扔垃圾。
【解决方案2】:

DWORD 定义为无符号 32 位整数 [MSDN]。

unsigned long 保证至少是一个 32 位无符号整数 [cppreference]。

因此,您始终可以将DWORD 分配给unsigned long 而不会损失精度,因此您可以将unsigned long 说明符与printf 一起使用,以及将参数显式转换为unsigned long。

另一种方法是使用std::uint32_t,它保证等于DWORD,并在printf 中使用该格式说明符。

【讨论】:

  • 我认为我们已经确定这是未定义的行为。但是好的,我的回答并没有直接解决这个问题,它只提供了一种符合标准的方法。
  • 回答这个问题的另一种方式:如果我可以编写一个完全符合 c++ 标准的编译器,它可以重新格式化你的硬盘,吃掉你的猫,或者诸如此类,如果它遇到你的代码,不管这可能是荒谬的,那么你的程序是未定义的。
猜你喜欢
  • 2020-06-15
  • 1970-01-01
  • 2011-08-25
  • 2011-08-23
  • 1970-01-01
  • 2011-12-31
  • 2023-01-27
  • 2011-08-26
相关资源
最近更新 更多