【问题标题】:Efficient way to bit-copy a signed integer to an unsigned integer将有符号整数位复制到无符号整数的有效方法
【发布时间】:2015-07-14 21:38:16
【问题描述】:
/* [1] */
int i = -1;
unsigned u = (unsigned)i;

/* [2] */
int i = -1;
unsigned u;
memcpy(&u, &i, sizeof i);

/* [3] */
int i = -1;
unsigned u = *(unsigned *)&i;

为了将有符号整数位复制到它的无符号伙伴,[1] 应该可以在大多数机器上工作,但据我所知,这并不能保证行为。

[2] 应该完全符合我的要求,但我想避免调用库函数的开销。

那么[3]呢?它是否有效地实现了我的意图?

【问题讨论】:

  • 严肃的问题 - 是什么让这个问题值得一票否决?
  • [1] 定义明确。是否满足你对“位拷贝”的要求是另一回事。
  • 我希望,虽然它当然不能保证,许多编译器将强度减少 memcpy() 调用。我认为这将是我的第一选择,因为它非常清楚地传达了意图(虽然我觉得sizeof u 更安全,但目的地的大小更重要)。
  • 只是出于好奇:你为什么需要这个?有什么实际用途,还是只是出于兴趣而问?
  • @xiver77 如果您不知道带符号数字的格式,那么您无法确定您的 rng 的 pmf 是否一致(或您规定的任何 pmf),因为不同的位值可以产生相同的整数值(特别是对于那些恭维和符号幅度)

标签: c++ c


【解决方案1】:
/* [4] */
union unsigned_integer
{
  int i;
  unsigned u;
};

unsigned_integer ui;
ui.i = -1;
// You now have access to ui.u

警告: 正如 cmets 中所讨论的,这在 CC++ 中的未定义行为似乎没问题,因为您的问题有两个标签,我将把它留在这里。有关更多信息,请查看此 SO 问题:

Accessing inactive union member and undefined behavior?

然后我会在C++ 中为reinterpret_cast 提供建议:

/* [5] */
int i = -1;
unsigned u = reinterpret_cast<unsigned&>(i);

【讨论】:

  • @Cheersandhth.-Alf 我没有提到它,因为我没有意识到这一点。 AFAIK 这完全等同于位副本,不是吗?究竟什么是未定义?
  • 不是(部分)最后一个给定值的联合成员的访问权限。
  • 另外,这种用法不是未定义的行为。但是,如果读取的值是陷阱表示,它可能会导致 UB。总结:类型双关实际上在 C89、C99(TC3 之后)和 C11 中是合法的。
  • 0 由于答案已更新,因此删除了我的反对票。不过,我认为赞成票是错误的,因为使用union 进行这种扭曲是没有优势的。
  • @Drax:值得注意的是,“只有在类型别名规则允许的情况下,才能安全地访问结果引用”,这在 C++11 标准的第 5.2.10 节中没有。这完全是为该段落做出贡献的作者所赞同的解释。它是 gcc/g++ 领域的常见解释,它适用于该编译器,但标准未指定。
【解决方案2】:
/* [1] */
int i = -1;
unsigned u = (unsigned)i;

↑ 这保证不会在符号和大小或 1 的补码机器上工作,因为转换为无符号保证会产生模 2 的有符号值n 其中 n 是无符号类型中的值表示位数。 IE。保证转换产生相同的结果就好像有符号类型使用二进制补码表示。


/* [2] */
int i = -1;
unsigned u;
memcpy(&u, &i, sizeof i);

↑ 这会很好地工作,因为类型保证具有相同的大小。


/* [3] */
int i = -1;
unsigned u = *(unsigned *)&i;

↑ 这在 C++11 及更早版本中是正式的未定义行为,但它是标准中“严格别名”子句中包含的情况之一,因此可能所有现存的编译器都支持它。此外,它也是reinterpret_cast 的一个示例用于。在 C++14 及更高版本中,关于未定义行为的语言已从 (1)关于左值到右值转换的部分中删除。

如果我这样做了,为了清楚起见,我会使用命名的 C++ 转换。

然而,我会尝试有时看起来标准的允许我做的不切实际的事情编译器必须说的话,特别是 g++ 及其严格的别名选项,无论它是什么,还有clang,因为它被设计为g++的直接替代品。

至少如果我计划使用这些编译器和选项的代码。


1) [conv.lval], §4.1/1 在 C++11 和 C++14 中。

【讨论】:

  • 第三个选项不是未定义的行为,因为它被列为例外。
  • 至少在C语言中,第三个例子是明确定义的。根据 C89 §3.3.16.1:“如果存储在一个对象中的值是从另一个对象访问的,该对象以任何方式与第一个对象的存储重叠,那么重叠应该是准确的,并且两个对象应该有合格或不合格的版本兼容类型;否则行为未定义”。并且标准非常明确,仅在限定符或符号上不同的类型是兼容类型。
  • @ErlendGraff:谢谢。对不起,我只回答了 C++。这些多语言问题很烦人。固定。
  • @SiyuanRen:请注明标准的语言和相关段落。对于 C++,请注意,reinterpret_cast 部分未引用所谓的严格别名子句(旨在枚举保证 UB 的情况)。
  • @anonymous downvoter:请解释您的反对意见,以便改进答案,或者其他人可以忽略投票。谢谢。
【解决方案3】:

这是来自文档 N3797 的第 4.7 段“积分转换”,这是 C++14 标准的最新工作草案:

如果目标类型是无符号的,则结果值是最小的 与源整数一致的无符号整数(模 2n 其中 n 是 用于表示无符号类型的位数)。 [注:在一个 二进制补码表示,这种转换是概念性的,并且 位模式没有变化(如果没有截断)。 ——尾注]

大致上,世界上所有的计算机都使用二进制补码表示。所以 [1] 是要走的路(除非您将 C++ 移植到 IBM 7090)。

【讨论】:

  • 你忘了提到 Univac。终于停产了吗?
  • @Cheersandhthf:我无法想象 Univac 是如何让我忘记的。我什至见过一次,大约在 1982 年。(它没有 C++ 编译器。)
【解决方案4】:

[3] 在 C 和 C++ 中都是正确的(从 C++14 开始,但以前不是);在这种情况下不需要使用memcpy。 (也就是说,没有理由使用memcpy,因为它可以有效地传达您的意图,显然是安全的,并且开销为零。)

C,6.5 表达式

7 - 一个对象的存储值只能由具有以下之一的左值表达式访问 以下类型:[...]

  • 对应于有效类型的有符号或无符号类型 对象,[...]

C++,[basic.lval]

10 - 如果程序尝试通过非左值的左值访问对象的存储值 以下类型的行为未定义:[...]

  • 对应于对象动态类型的有符号或无符号类型,[...]

如您所见,两个标准中的措辞非常相似,因此可以在两种语言中使用。

【讨论】:

  • C++11 §4.1/1 " 如果泛左值引用的对象不是 T 类型的对象,也不是从 T 派生的类型的对象,或者如果该对象未初始化,需要进行此转换的程序具有未定义的行为”我看到您在your own answer 中提到了另一个问题。使用联合技术我们有一个正确类型的对象但没有初始化,使用reinterpret_cast 我们有初始化但没有正确类型的对象。
  • @Cheersandhth.-当然,这就是 [conv.lval] 为 C++14 修复的原因;在 C++11 中,该部分与 [basic.lval] 不一致。
  • 谢谢,我不知道。我认为值得在答案中指出(在 C++14 及更高版本中有效)。我会修好我的。
  • @Cheersandhth.-Alf DR 616 是改变的地方,顺便说一句。感谢您指出我的旧答案 - 即使在标准的“次要”修订中看到事情发生了怎样的变化,这当然很有趣!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-10
  • 2013-08-29
  • 2010-09-19
  • 2013-10-02
  • 1970-01-01
  • 2012-03-23
  • 1970-01-01
相关资源
最近更新 更多