对于嵌入式代码,为什么我应该使用“ uint_t”类型而不是“ unsigned int”类型?


22

我正在使用gcc在c中为STM32F105编写应用程序。

在过去的(有比较简单的项目),我一直定义为变量charintunsigned int,等等。

我看,这是普遍使用在stdint.h中定义的类型,如int8_tuint8_tuint32_t,等。这是在多个API的,我使用,并且还从ST的ARM CMSIS库真。

我相信我理解为什么我们应该这样做;使编译器可以更好地优化内存空间。我希望可能还有其他原因。

但是,由于c的整数提升规则,每次尝试添加两个值,进行按位运算等操作时,我都会不断遇到转换警告,警告显示为conversion to 'uint16_t' from 'int' may alter its value [-Wconversion]。在这里这里讨论这个问题。

使用声明为int或的变量时不会发生这种情况unsigned int

给出几个示例,考虑到这一点:

uint16_t value16;
uint8_t value8;

我将不得不更改此:

value16 <<= 8;
value8 += 2;

对此:

value16 = (uint16_t)(value16 << 8);
value8 = (uint8_t)(value8 + 2);

很难看,但是如果需要的话我可以做。这是我的问题:

  1. 是否有一个地方从转换的情况下,未签名签署和返回无符号将使结果不正确的?

  2. 使用/反对使用stdint.h整数类型还有其他重要原因吗?

根据我收到的答案,它看起来像stdint.h类型通常是优选的,既是C转换uintint和背部。这导致了一个更大的问题:

  1. 我可以通过使用类型转换(例如value16 = (uint16_t)(value16 << 8);)来防止编译器警告。我只是隐藏问题了吗?有更好的方法吗?

使用无符号文字:ie 8u2u
停止危害莫妮卡2014年

谢谢@OrangeDog,我想我误会了。我同时尝试了value8 += 2u;value8 = value8 + 2u;,但得到了相同的警告。
2014年

无论如何都要使用它们,以在没有宽度警告时避免出现已签署的警告:)
停止危害Monica 2014年

Answers:


11

遵循标准的编译器int(介于17到32位之间)可以使用以下代码合法地执行其所需的任何操作:

uint16_t x = 46341;
uint32_t y = x*x; // temp result is signed int, which can't hold 2147488281

想要这样做的实现可以合法地生成一个程序,除了使用每种可想象的协议在每个端口引脚上重复输出字符串“ Fred”之外,什么都不做。将程序移植到将执行此操作的实现的可能性极低,但从理论上讲是可能的。如果要编写上面的代码,以确保不会发生未定义的行为,则必须将后一个表达式编写为(uint32_t)x*x1u*x*x。在int介于17到31位之间的编译器上,后一个表达式将超出高位,但不会出现未定义行为。

我认为gcc警告可能试图暗示所编写的代码并非完全100%可移植。有时确实应该编写代码来避免某些实现中未定义的行为,但是在许多其他情况下,人们应该简单地认为该代码不太可能被用于执行会令人讨厌的事情的实现。

请注意,使用int和这样的类型short可能会消除一些警告并解决一些问题,但可能会导致其他问题。像uint16_t和C的整数提升规则之间的类型交互很棘手,但此类类型可能仍比任何其他类型都要好。


6

1)如果只是将无符号的整数转换为相同长度的有符号整数,并且之间没有任何操作,则每次都会得到相同的结果,因此这里没有问题。但是各种逻辑和算术运算对有符号和无符号操作数的作用不同。
2)使用stdint.h类型的主要原因是,此类类型的位大小在所有平台上均已定义且相等,这对于intlong等不适用,char并且没有标准签名,可以通过以下方式进行签名或不签名:默认。无需额外的检查和假设,就可以更轻松地操作知道确切大小的数据。


2
定义平台的所有平台上,int32_t和的大小uint32_t相等。如果处理器没有完全匹配的硬件类型,则不会定义这些类型。因此的优势等等,也许,等intint_least32_t
皮特贝克尔

1
@PeteBecker-可以说这是一个优势,因为产生的编译错误使您立即意识到问题所在。我宁愿这样做,也不愿改变类型。
sapi 2014年

@sapi-在许多情况下,底层大小无关紧要;多年以来,C语言程序员在没有固定大小的情况下相处得很好。
皮特·贝克尔

6

由于Eugene的#2可能是最重要的一点,所以我想补充一点,这是

MISRA (directive 4.6): "typedefs that indicate size and signedness should be used in place of the basic types".

Jack Ganssle似乎也支持该规则:http ://www.ganssle.com/tem/tem265.html


2
太糟糕了,没有用于指定“可以安全地与任何其他相同大小的整数相乘以产生相同大小结果的N位无符号整数”的类型。整数提升规则与现有类型(如uint32_t
supercat 2014年

3

消除警告的一种简单方法是避免在GCC中使用-Wconversion。我认为您必须手动启用此选项,但如果没有,则可以使用-Wno-conversion禁用它。如果仍然需要,可以通过其他选项启用符号和FP精度转换的警告。

-Wconversion警告几乎总是假阳性,这也许就是为什么-Wextra在默认情况下也不启用它的原因。一个堆栈溢出问题,有很多好的选项设置建议。根据我自己的经验,这是一个不错的起点:

-std = c99 -pedantic -Wall -Wextra -Wshadow

如果需要,可以添加更多,但是您不会。

如果必须保留-Wconversion,则可以通过仅强制转换数字操作数来稍微缩短代码:

value16 <<= (uint16_t)8;
value8 += (uint8_t)2;

但是,如果不突出显示语法,这不容易阅读。


2

在任何软件项目中,使用可移植类型定义都非常重要。(即使是同一编译器的下一版本也需要考虑这一点。)一个很好的例子,几年前,我工作了一个项目,当前的编译器将'int'定义为8位。编译器的下一个版本将“ int”定义为16位。由于我们没有对“ int”使用任何可移植的定义,因此ram的大小(有效地)增加了一倍,并且许多依赖于8bit int的代码序列都失败了。使用可移植类型定义可以避免该问题(修复数百个工时)。


不应使用任何合理的代码int来引用8位类型。即使像CCS这样的非C编译器这样做,合理的代码也应使用char8位的一个或类型定义的类型,以及16位的类型定义的类型(不是“ long”)。另一方面,即使代码使用适当的typedef,将代码从CCS之类的代码移植到实际的编译器也容易出现问题,因为这样的编译器在其他方面常常是“不寻常的”。
supercat 2014年

1
  1. 是。一个n位有符号整数可以表示一个n位无符号整数的大约一半,而依靠溢出特征是未定义的行为,因此任何事情都可能发生。当前和过去的绝大多数处理器都使用二进制补码,因此很多操作碰巧对有符号和无符号整数类型执行相同的操作,但是即使那样,并非所有操作都会产生按位相同的结果。当您无法弄清楚为什么代码无法按预期工作时,您确实会在以后遇到额外的麻烦。

  2. 尽管intunsigned具有实现定义的大小,但是出于大小或速度方面的考虑,实现通常“聪明地”选择这些大小。我通常会坚持这些,除非我有充分的理由这样做。同样,在考虑使用int还是unsigned时,我通常更喜欢int,除非我有充分的理由这样做。

在确实需要更好地控制类型的大小或符号性的情况下,我通常会更喜欢使用系统定义的typedef(size_t,intmax_t等)或使用自己的typedef来指示给定的功能类型(prng_int,adc_int等)。


0

该代码通常用于ARM拇指和AVR(以及x86,powerPC和其他体系结构),并且16或32位在STM32 ARM上可以更高效(两种方式:闪存和循环),即使对于适合8位(在AVR上8位更有效。但是,如果SRAM几乎已满,则可以将全局变量恢复为8位(但对于本地变量则不行)。为了可移植性和维护性(尤其是8位var),指定MINIMUM合适的大小(而不是在一个.h位置(通常在ifdef下)指定确切的大小和typedef)(有可能是uint_fast8_t /)是有利的(没有任何不利之处)。uint_least8_t)在移植/构建期间,例如:

// apparently uint16_t is just as efficient as 32 bit on STM32, but 8 bit is punished (with more flash and cycles)
typedef uint16_t uintG8_t; // 8bit if SRAM is scarce (use fol global vars that fit in 8 bit)
typedef uint16_t uintL8_t; // 8bit on AVR (local var, 16 or 32 bit is more efficient on STM + less flash)
// might better reserve 32 bits on some arch, STM32 seems efficient with 16 bits:
typedef uint16_t uintG16_t; // 16bit if SRAM is scarce (use fol global vars that fit in 16 bit)
typedef uint16_t uintL16_t; // 16bit on AVR (local var, 16 or 32 bit whichever is more efficient on other arch)

GNU库有所帮助,但是通常typedef还是有意义的:

typedef uint_least8_t uintG8_t;
typedef uint_fast8_t uintL8_t;

//但不考虑SRAM时,两者均为uint_fast8_t。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.