【问题标题】:What's the point in specifying unsigned integers with "U"?用“U”指定无符号整数有什么意义?
【发布时间】:2017-05-02 14:48:15
【问题描述】:

从我记事起,我就一直这样做:

for (unsigned int i = 0U; i < 10U; ++i)
{
    // ...
}

换句话说,我在无符号整数上使用U 说明符。现在已经看了太久了,我想知道我为什么要这样做。除了表示意图之外,我想不出它在这样的琐碎代码中有用的原因吗?

我应该继续使用这个约定是否有正当的编程原因,或者它是多余的?

【问题讨论】:

  • Meaning of U suffix的可能重复
  • 稍微改变了问题
  • 10 是一个有符号整数。 10U 是一个无符号整数。所以也许你这样做是为了避免隐式类型转换。但是,在大多数情况下(当然在您发布的示例中),隐式类型转换无论如何都会完美地工作。但是,如果有办法避免它们,那么这样做有什么害处呢?我只是猜测。
  • 从链接的答案看来,只有当我认为溢出可能是一个问题时,保留它似乎才有用?但是话又说回来,我不是首先通过指定unsigned 来缓解这种情况,而没有U 吗?
  • 不客气。还有另一个例子,在使用 C++11 关键字 auto 的变量定义中使用它们。见this answer。 (我比上一个更不喜欢它也无关紧要,但不知何故,我觉得有必要提一下。)

标签: c unsigned convention


【解决方案1】:

首先,我将说明对您来说可能显而易见的内容,但您的问题留有余地,因此我要确保我们都在同一页面上。

无符号整数和常规整数之间有明显的区别:它们的范围不同(int32 为 -2,147,483,648 到 2,147,483,647,uint32 为 0 到 4,294,967,295)。当您使用正确的位移 &gt;&gt; 运算符时,最高有效位的位会有所不同。

当您需要告诉编译器将常量值视为 uint 而不是常规 int 时,后缀很重要。如果常量在常规 int 范围之外但在 uint 范围内,这可能很重要。如果您不使用U 后缀,编译器可能会抛出警告或错误。

除此之外,Daniel Daranas 在 cmets 中提到唯一会发生的事情:如果您不使用 U 后缀,您将隐式地将常量从常规 int 转换为 uint。这对编译器来说是一点点额外的工作,但没有运行时差异。

你应该关心吗?这是我的答案,(粗体字,对于那些只想快速回答的人):确实没有充分的理由将常量声明为10U0U 大多数时候,你' re 在 uint 和 int 的共同范围内,因此该常量的值无论是 uint 还是 int 看起来都完全相同。编译器将立即获取您的 const int 表达式并将其转换为 const uint。

也就是说,这是我可以为您提供的唯一论据:语义。使代码在语义上一致是很好的。在这种情况下,如果您的变量是一个 uint,那么将该值设置为一个常量 int 是没有意义的。如果你有一个 uint 变量,那显然是有原因的,它应该只适用于 uint 值。

不过,这是一个非常薄弱的​​论点,特别是因为作为读者,我们接受 uint 常量通常看起来像 int 常量。我喜欢一致性,但使用“U”没有任何好处。

【讨论】:

  • 很好的解释——证实了一些我一直认为理所当然的事情。
  • 在某些情况下,位移和整数常量不会 100% 混合,并且(只是)编译器警告可以通过显式地使常量无符号来缓解。
  • "如果您不使用 U 后缀,编译器可能会在这种情况下抛出警告或错误。"不正确,至少从 C99 开始。如果文字常量不能放入int,编译器会将其设为longlong long;只有当它找不到足够大的有符号类型时才会产生诊断。
  • 我最近在数组中使用了一点 C#。该语言坚持下标是uint。另一个烦恼是byte x = 1; 工作正常,但byte y = 1+1; 是类型不匹配。也许 OP 的思想被 C# 损坏了....
【解决方案2】:

我在使用定义来避免有符号/无符号不匹配警告时经常看到这种情况。我使用不同的工具链为多个处理器构建了一个代码库,其中一些非常严格。

例如,删除下面定义的 MAX_PRINT_WIDTH 中的“u”:

#define MAX_PRINT_WIDTH          (384u)
#define IMAGE_HEIGHT             (480u)   // 240 * 2
#define IMAGE_WIDTH              (320u)   // 160 * 2 double density

给出以下警告: “..\Application\Devices\MartelPrinter\mtl_print_screen.c”,第 106 行:cc1123:{D} 警告: 无符号类型与有符号类型的比较

for ( x = 1; (x < IMAGE_WIDTH) && (index <= MAX_PRINT_WIDTH); x++ )

您可能还会看到“f”代表 float 和 double。

【讨论】:

  • 这是一个很好的观点。现在我想起来了,我想我开始到处使用它正是因为宏。
【解决方案3】:

我从评论中提取了这句话,因为它被广泛认为是不正确的陈述,还因为它让我们深入了解了为什么明确标记无符号常量是一个好习惯。

...似乎只有在我认为溢出可能是一个问题时才保留它才有用?但是话又说回来,我不是首先通过指定 unsigned 来缓解这种情况吗...

现在,让我们考虑一些代码:

int something = get_the_value();
// Compute how many 8s are necessary to reach something
unsigned count = (something + 7) / 8;

那么,unsigned 是否可以缓解潜在的溢出?一点也不。

假设something 变成INT_MAX(或接近该值)。假设一台 32 位机器,我们可能期望 count 为 229,即 268,435,456。 But it's not.

告诉编译器计算的结果应该是unsigned 对计算的类型没有任何影响。由于somethingint,而7intsomething + 7 将被计算为int,并且会溢出。然后溢出的值将除以 8(也使用有符号算术),任何结果都将转换为 unsigned 并分配给 count

使用 GCC,算术实际上是在 2s 补码中执行的,因此溢出将是一个非常大的负数;除法之后,它会是一个不太大的负数,最终是一个更大的无符号数,比我们预期的要大得多。

假设我们指定了7U(为了保持一致,也许还指定了8U)。 Now it works.。它之所以有效,是因为现在 something + 7U 是使用 unsigned 算术计算的,它不会溢出(甚至回绕)。

当然,这个错误(以及成千上万的类似错误)可能会在很长一段时间内被忽视,在最糟糕的时刻(可能是字面意思)爆炸......

(显然,将something设为无符号缓解问题。在这里,这很明显。但定义可能距离使用还有很长的路要走。)

【讨论】:

  • 有趣。我很高兴我有一个好习惯,可以根据需要指定带或不带Uall 整数,即使我完全忘记了为什么要这样做。很高兴阅读计算解释 - 我希望我可以将两个答案标记为已接受!
【解决方案4】:

您应该为琐碎的代码执行此操作的一个原因1 是后缀强制在文字上使用类型,并且类型对于产生正确的结果可能非常重要。

考虑一下这段(有点傻的)代码:

#define magic_number(x) _Generic((x), \
                          unsigned int : magic_number_unsigned, \
                          int          : magic_number_signed \
                        )(x)

unsigned magic_number_unsigned(unsigned) {
  // ...
}

unsigned magic_number_signed(int) {
  // ...
}

int main(void) {
  unsigned magic = magic_number(10u);
}

不难想象这些函数实际上根据参数的类型做了一些有意义的事情。如果我省略了后缀,那么对于一个非常微不足道的调用,泛型选择会产生错误的结果。


1 但也许不是您帖子中的特定代码。

【讨论】:

    【解决方案5】:

    在这种情况下,它完全没用。

    在其他情况下,后缀可能有用。例如:

    #include <stdio.h>
    
    int
    main()
    {
      printf("%zu\n", sizeof(123));
      printf("%zu\n", sizeof(123LL));
      return 0;
    }
    

    在我的系统上,它会打印 4 然后 8。

    但是回到您的代码,是的,它使您的代码更加明确,仅此而已。

    【讨论】:

    • 实际上是因为LL 而不是u%d 说明符错误。
    • 我试图说明我的观点,而不是编写生产就绪代码...
    • 不清楚为什么使用LL后缀来讨论使用U后缀的区别。 LL 经常导致大小变化,U 不经常发生。建议printf("%zu\n", sizeof(4000000000)); printf("%zu\n", sizeof(4000000000U)); 显示与U 的大小差异。
    • 我说的是一般的后缀,而不是U
    猜你喜欢
    • 1970-01-01
    • 2011-08-10
    • 1970-01-01
    • 2012-02-14
    • 1970-01-01
    • 1970-01-01
    • 2011-11-10
    • 2022-01-20
    相关资源
    最近更新 更多