C语言 int、unsigned int、uint_fastN_t和int_fastN_t的优缺点是什么?

hrirmatl  于 2023-10-16  发布在  其他
关注(0)|答案(3)|浏览(115)

我有一个变量,类型无关紧要;可能的范围将容易地适合于任何上述类型。例如循环计数器。等
使用的优点和缺点是什么:

  • int
  • unsigned int
  • int_fastN_t
  • uint_fastN_t
  • 请注意任何潜在的接近选民:*我不是在问使用哪一个,因为这将是基于意见的,但我问,当选择一个而不是另一个时,我应该考虑什么因素?
p1tboqfb

p1tboqfb1#

  • 签名 * 与 * 未签名***
  • 对于逻辑和模式数据操作,首选 unsigned 类型。
  • 如果遵循文档化的算法,则匹配其符号(如果指定了符号)。
  • 使用 signed 类型进行常规数据操作。
  • 避免在等式中混合类型的符号。
  • 使用size_tunsigned 类型)来调整数组大小和索引。
  • 使用 signed 类型的操作很容易导致UB @Lundin,因此 unsigned 类型在关键应用程序中更受欢迎。
  • 快不快**
  • 避免使用(u)int_fastN_t,除非确实需要。也许当一个狭窄的类型是必要的,但速度是关键。它们不值得一时兴起使用。
  • fast类型要求文档证明其使用。
  • 永远不要把...fast..作为位域。
  • (u)int_fastN_t很少有用。
  • 便携性:自C99以来,(u)int_fastN_t/(u)int_leastN_t始终可用,并且与 * 可选 * (u)intN_t相比,在可移植性方面略有优势。

int/unsigned自C语言诞生以来就一直可用。

请记住, 快速 * 类型是一种妥协。 快速 * 并不总是在所有用例中最快。*

x6yk4ghg

x6yk4ghg2#

这里有一些因素要考虑,使您的选择:

  • 如果边界是整数文字,则使用经典的
for (int i = 0; i < 10; i++) { 
    ...
}

理由:越简单越好。

  • 如果边界是变量,则使用与边界相同的类型。
size_t len = strlen(s);
for (size_t i = 0; i < len; i++) {
    ...
}
  • 如果边界可能超出int的范围,请使用更大的类型:longint64_t.使用int_fastN_tint_leastN_t会使代码更难阅读,并且在大多数体系结构上不会产生差异。
6ojccjat

6ojccjat3#

*int

这种类型几乎只有一个好处,那就是向后兼容非关键代码,其中整数限制/大小无关紧要。你几乎不应该使用它,除非你被老C90的向后兼容性问题所困扰。

*unsigned int

int相同的参数-此类型主要用于旧代码中,其中整数大小无关紧要。但是它稍微更有用一些,因为它可以被视为一种安全的通用类型,可以在受到隐式整数提升的表达式中使用。如果你使用unsigned int或者强制转换一个操作数,那么它就不再受到整数提升,默认参数提升等的影响。您也可以在按位算术中安全地使用unsigned int,其中应避免使用int
此外,像shortsigned char等小整数类型也是同样危险的,因为它们会受到隐式提升,由于整数提升而导致的潜在符号变化,由于提升而隐藏溢出/回绕的可能性等。

*intn_t(其中 n 对应于目标的字大小)

int相比,intn_t具有便携和固定宽度的巨大优势。但前提是 n 足够大,不会使操作数成为整数提升的对象,例如32/64位计算机上的int32_t
intn_t还有一个很大的优点,就是可以保证是2的补码形式,这对于int来说是不能保证的(直到C23的发布)。
一个小细节是,除非目标支持intn_t,否则不能保证它得到支持。这并不适用于99%的真实系统,只适用于一些具有16位字节的古怪DSP CPU。
值得注意的是,intn_t应该 * 仅 * 用于实际需要有符号数字的地方。如果你不需要带符号的数字,那么就使用uintn_t

*uintn_t

intn_t的所有好处都是相同的,但还有另一个好处,即它也可以安全地用于按位算术。使其成为最通用的整数类型。

*int_fastn_t

这主要用于手动微优化,其中大小并不重要,但您怀疑较大的整数类型会导致更快的代码。在编写可广泛移植的代码时,它也有它的用途。
例如,如果您开发的算术代码至少需要16位,并且应该在16位和32位CPU上运行。在int16_t上进行运算可能不是最佳的,因为有限的指令,对齐等。但是如果你在int_fast16_t上做算术,那么它就不会像32位算术那样不必要地增加16位的负担,因为在那个目标上类型仍然是16位。但与此同时,它仍然允许32苦选择一个更大的类型,如果这给更好的性能。
另一个优点是int_fastn_t对于2的补码目标的 all C编译器是强制性的,包括独立的实现(嵌入式系统,古怪的DSP编译器等)。
相对于intn_t的缺点是int_fastn_t * 不是 * 固定的宽度,确切的宽度很重要,并且使用int_fastn_t强制执行手动的速度超过大小优化,我们通常可以让选择的编译器选项来决定。

*uint_fastn_t

int_fastn_t相同的优点/缺点,但也适用于按位算术。

*int_leastn_t / uint_leastn_t

这些都是非常特殊用途的古怪类型。基本上,你会说“我需要变量至少是 n 位,但除此之外,你可以随心所欲”。与fast类型不同,这给了编译器更大的自由,可以选择大小或速度优化,也可以根本不优化。
当您需要优化大小并同时在多个目标之间进行移植时,这些类型的保存并没有太多用处。例如,在我上面的16位与32位CPU的例子中,int_least16_t在16位CPU上仍然是16位,但在32位CPU上,编译器可能会选择将其保留为16位,以防给予大小上的好处。
例如,支持VLE编码的32位PowerPC(基本上:如果可能的话,使用16位指令)可能会受益于least类型,因为无论机器码是否使用VLE,您都可以保持C代码不变。

拾荒者程序

我拼凑了一个TL;整型选择器。根据#define调整true/false,让它选择合适的整数类型。
使用gcc 13.2 -std= c2 x进行测试。https://godbolt.org/z/zz9GTf35M

// pick true/false for each of these parameters:
#define CODE_NEEDS_TO_BE_WIDELY_PORTABLE           true
#define CODE_MUST_BE_C90                           false
#define I_NEED_NEGATIVE_NUMBERS                    false
#define I_NEED_BITWISE_ARITHMETIC                  false
#define I_CARE_ABOUT_NUMERICAL_LIMITS              true
#define I_HAVE_SPECIAL_OPTIMIZATION_REQUIREMENTS   false

_Static_assert(!(CODE_NEEDS_TO_BE_WIDELY_PORTABLE && CODE_MUST_BE_C90), 
                "Contradicting requirements. C90 isnt portable.");

#include <stdio.h>

int main (void)
{
  if(CODE_NEEDS_TO_BE_WIDELY_PORTABLE)
    if(!I_NEED_NEGATIVE_NUMBERS || I_NEED_BITWISE_ARITHMETIC)
      if(!I_HAVE_SPECIAL_OPTIMIZATION_REQUIREMENTS)
        puts("Use uintn_t");
      else
        puts("Use uint_fastn_t/uint_leastn_t depending on the situation.");
    else
      if(!I_HAVE_SPECIAL_OPTIMIZATION_REQUIREMENTS)
        puts("Use intn_t");
      else
        puts("Use uint_fastn_t/uint_leastn_t depending on the situation.");
  else
    if(!I_NEED_NEGATIVE_NUMBERS || I_NEED_BITWISE_ARITHMETIC)
      if(!CODE_MUST_BE_C90 || I_CARE_ABOUT_NUMERICAL_LIMITS)
        puts("Use uintn_t");
      else if(CODE_MUST_BE_C90 && I_CARE_ABOUT_NUMERICAL_LIMITS)
        puts("Use home-brewed type system replicating uintn_t from stdint.h");
      else
        puts("Use unsigned int");
    else
      if(!CODE_MUST_BE_C90 || I_CARE_ABOUT_NUMERICAL_LIMITS)
        puts("Use intn_t");
      else if(CODE_MUST_BE_C90 && I_CARE_ABOUT_NUMERICAL_LIMITS)
        puts("Use home-brewed type system replicating intn_t from stdint.h");
      else
        puts("Use int");
}

相关问题