【问题标题】:C / C++ best practices with signed / unsigned ints and function calls带符号/无符号整数和函数调用的 C/C++ 最佳实践
【发布时间】:2016-09-26 11:38:46
【问题描述】:

我针对两种不同的语言提出这个问题:C 和 C++。

在调用与我们在代码中需要的整数符号期望相反的函数时,最佳做法是什么?

例如:

uint32       _depth;                        // uint32 = DWORD
int          depth;

_BitScanForward(&_depth, (uint32)input);    // DWORD, DWORD
depth = (int)_depth;

_BitScanForward 需要 DWORD (uint32) 参数。变量input 是int16 类型,我需要在我的代码中将结果_depth 处理为int32。

  1. 我是否需要关注input 的转换,如图所示?我知道编译器可能会为我做这件事,但最佳做法是什么?
  2. 是否可以将 _depth 声明为 int32 从而避免事后强制转换,如图所示?

注意:

我对编译器的评论是基于经验。我编写了在 VS 中编译时没有警告但在执行时崩溃的代码。原来我正在调用一个宽度不正确的函数。所以我不再把这个话题留给编译器了。

编辑:

答案很有帮助,谢谢。让我完善我的问题。如果没有宽度问题,即函数不期望比传入的 int 更窄(显然会失败),那么可以依靠编译器来处理符号和宽度差异吗?

【问题讨论】:

  • "我可以将 _depth 声明为 int32" 那么,有什么能阻止你这样做吗?
  • “可能”为你做?
  • 您使用哪种语言? C 还是 C++?这是两种截然不同的语言。选一个!
  • @DanielDaranas “可以”的定义 = “用于表示能力或许可”。许可为可接受。但我只是为你改变了我的问题;)
  • 请一次要求一种语言。如果您命名的每种语言的答案都不同,那会让事情变得一团糟!当然,您实际上是在使用其中一种,所以只需说出那个。

标签: c++ c


【解决方案1】:

我强烈建议将该函数隐藏到与您的首选 API 一致的自定义包装函数中(并在此函数中进行适当的显式转换)。在使用特定于编译器的函数的情况下,这具有额外的优势,即只需重新实现该包装函数,就可以更容易地将其移植到不同的编译器(如果你想这样做的话)。

【讨论】:

  • 这是一个很好的跨库操作的解决方案。好主意!
【解决方案2】:

当从比int 窄的任何整数类型转换为与int 相同或宽的任何整数类型时,编写显式转换非常重要。如果您不这样做,由于“整数提升”规则,编译器会首先将值转换为int,然后再转换为目标类型。这几乎总是错误的,如果我们今天从头开始,我们不会这样设计语言,但为了兼容性,我们坚持使用它。

uint16_tuint32_tWORDDWORD 等系统提供的 typedef 可能更窄、更宽或与 int 大小相同;在 C++ 中,您可以使用模板来解决问题,但在 C 中则不能。因此,您可能希望为涉及这些的任何转换编写显式转换。

【讨论】:

  • 在决定将短的无符号值提升为有符号时,C 标准的作者指出,当时的大多数编译器对于 unsigned mul(unsigned short x, unsigned short y) { return x*y; } 这样的构造都会以算术正确的方式可靠地运行,即使对于结果范围为 INT_MAX+1 到 UINT_MAX。然而,出于某种原因,gcc 的维护者认为让“优化器”生成仅对 INT_MAX 以内的值可靠的代码会更有用。
  • @IamIC 在您的情况下这仍然是最佳实践,因为 uint32int 可能并不总是相同的大小;例如,如果您曾经将代码移植到另一个环境。回复malloc,不幸的是,执行该操作的唯一可靠方法比适合单个表达式要复杂得多,请参阅lteo.net/blog/2014/10/28/…(阅读全部内容)。
  • @IamIC:表达式会引起麻烦的唯一方法是乘积超过 size_t 的最大值。如果“int”是 16 位,但“size_t”是 8 位,“sizeof(MyType)”是 200,而“i”是 240,则乘法将产生未定义行为,而不是计算 0xC8u * 0xF0u(即 0xBB80)和将“malloc”值传递给 0x80 (128)。另一方面,添加强制转换以便代码将 malloc() 传递一个 128 的值而不是在乘法中调用 UB 可能不是很有用。
  • @IamIC 在我看来,supercat 引用的问题并不像在您期望它们是零扩展的地方进行符号扩展的问题那么重要,反之亦然。
  • @zwol:我想说的是代码有时在罕见但不可预测的情况下可能会出现奇怪的行为这一事实比代码以一种确定性和逻辑但不符合应用程序的需求。 gcc 对上述函数所做的有趣“优化”不仅会产生意想不到的值——例如gcc 认识到一个参数将始终为 65535,它可能会得出另一个参数不能超过 32768 的结论并省略其他代码,这些代码仅在出现这种情况时才相关。
【解决方案3】:

嗯,这取决于您的使用情况等:

如果我可以使用需要的类型,我就使用类型。

如果不是: 如果您隐式转换可能导致上溢/下溢的数据类型,您的编译器应该警告您。所以我通常会发出这些警告,并将隐式转换更改为显式转换。

我有两种不同的方法:

如果我 100% 确定我永远不会溢出/下溢有符号/无符号 int 之间的界限,我会使用 static_cast。 (通常用于不同 API 的转换。比如 size() 返回 int 与 size_t)。

当我不确定或可能超出我使用boost::numeric_cast 的界限时。当您投射超出边界时,这会引发异常,从而显示何时发生这种情况。

除此之外的方法遵循在出现问题时硬失败/崩溃/终止的做法,而不是继续处理损坏的数据,然后在其他地方崩溃或对未定义的数据执行其他操作。

【讨论】:

    【解决方案4】:

    首先,您的编译器会将强制转换设为隐式,并在任何有意义的警告级别上向您发出警告。

    您执行的两种转换都是编译器(或您的同事)无法轻易确定它们是否正确的转换,因此使用边界测试进行显式转换或显式转换是最佳实践。您选择哪个取决于您对数据的了解。最安全的方法是检查边界条件。最便宜的方法是简单地转换(在 C++ 中请使用 static_cast 而不是 C 风格的转换)。

    【讨论】:

    • 我不清楚为什么我的演员阵容对编译器/人类来说是不清楚的。请解释一下。
    • 你的第一次从有符号到无符号的转换,编译器总是不清楚。因为它不知道您的签名变量是负数还是正数。第二个演员表未签名到已签名。在这里,编译器无法确定 unsigned int 的值是否大于最大有符号值。因此它必须警告你。你的同事也有同样的问题。通过只查看一小部分代码,他们无法判断您是否在做正确的事情。
    • @IamIC:通过使用 C++ reinterpret_cast,您将在源代码级别告诉您,您希望使用与 [u]int 相同的位模式,例如从 int32_t -1 到 @ 987654323@ 在价值方面。 Reinterpret_cast 不会编译为任何指令,它只是值类型的源级更改。这可能会有所帮助,如果您有一些设计不佳的 API,例如返回 int size(),但它绝不意味着返回负数等...总体而言,最佳实践当然是避免任何转换并使用正确的一路输入,在其他情况下是 cast+test。
    • @IamIC C 的类型不如 C++ 强,仍然是 explicit (是隐式的 :/ :D )类 C 转换比编译器警告要好。它表明程序员已经注意并验证了强制转换在所有情况下都产生了预期的结果。但总的来说,当 C++ 可用时,我认为今天没有任何理由使用 C。
    • @lamlC 我绝不会建议移除演员表。对不同符号的强制转换应始终明确写入。只是为了确保您(几周后)、您的编译器和您的同事理解,您确实想要进行此转换并且您知道这没有副作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-07
    • 2015-01-24
    • 1970-01-01
    • 1970-01-01
    • 2018-08-18
    • 1970-01-01
    • 2012-10-20
    相关资源
    最近更新 更多