使用 "U" 指定无符号整数有什么意义?

What's the point in specifying unsigned integers with "U"?

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

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

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

是否有合理的编程理由让我继续使用此约定,或者它是否多余?

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

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

#include <stdio.h>

int
main()
{
  printf("%zu\n", sizeof(123));
  printf("%zu\n", sizeof(123LL));
  return 0;
}

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

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

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

无符号整数和常规整数之间有明显的区别:它们的范围不同(int32 为 -2,147,483,648 到 2,147,483,647,uint32 为 0 到 4,294,967,295)。当您使用正确的移位 >> 运算符时,将哪些位放在最重要的位上有所不同。

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

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

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

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

不过,这是一个非常弱的论点,特别是因为作为 reader,我们接受 uint 常量通常看起来像 int 常量。我喜欢一致性,但使用 'U'.

没有任何好处

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

例如,删除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”表示浮点数与双精度数。

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

...it seems like it would only be useful to keep it when I think overflow might be an issue? But then again, haven't I gone some ways to mitigating for that by specifying unsigned in the first place...

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

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 没有任何影响 计算。由于 something 是一个 int,而 7 是一个 int,因此 something + 7 将被计算为 int,并且会溢出。然后溢出的值将除以 8(也使用带符号的算术),无论结果如何,都将转换为 unsigned 并分配给 count.

对于GCC,算术实际上是在2s补码中执行的,所以溢出将是一个非常大的负数;除法后它会是一个不太大的负数,最后是一个很大的无符号数,比我们预期的大得多。

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

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

(显然,使 something unsigned 缓解这个问题。在这里,这很明显。但是定义可能离使用还有很长的路要走。 )

你应该为琐碎的代码这样做的一个原因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 但也许不是您 post.

中的特定代码