【问题标题】:Why does itoa expect a signed character instead of an unsigned?为什么 itoa 期望有符号字符而不是无符号字符?
【发布时间】:2020-06-02 15:38:28
【问题描述】:

在使用 PIC24FJ128GB204 在 MPLAB X 中工作时学习嵌入式 C。

到目前为止,我大多听说应该在嵌入式设备上尽可能多地使用无符号类型(尤其是?),所以我开始使用 uint8_t 数组来保存字符串。但是,如果我从 stdlib.h 调用 itoa,它需要一个指向有符号字符 (int8_t) 数组的指针:

extern char * itoa(char * buf, int val, int base);

当我在无符号数组上使用 itoa 后尝试编译时,这一点特别清楚:

main.c:317:9: warning: pointer targets in passing argument 1 of 'itoa' differ in signedness
c:\program files (x86)\microchip\xc16\v1.36\bin\bin\../..\include/stdlib.h:131:15: note: expected 'char *' but argument is of type 'unsigned char *'

在其他平台上搜索itoa的实现,这似乎是常见的情况。

这是为什么呢?

(我还注意到大多数实现都需要值/指针/基数,而-出于某种原因-Microchip 的 stdlib.h 首先需要指针。我花了一段时间才意识到这一点。)

【问题讨论】:

标签: c embedded pic mplab itoa


【解决方案1】:

char 作为 signedunsigned 是几十年前的折衷方案 - 当时为当时的编译器带来一定程度的一致性是有意义的。

itoa(),虽然不是标准的 C 库函数,但遵循该约定,因为 stringchar 组成。

许多库函数使用字符串 指针。 itoa() 也是如此,并将内部工作作为unsigned char 处理。请记住,string 是表示 text,而不是数字 - 所以 char 本身的签名并不是一个大问题。当然itoa()的重点是取一个数字(int)并形成一个字符串

C 库在许多情况下将char 功能“视为”unsigned char

  • int fgetc() 返回EOFunsigned char 范围内的值。

  • printf() "%c": "int 参数被转换为unsigned char,并且 结果字符被写入。”

  • <string.h> “对于本小节中的所有函数,每个字符都应被解释为具有unsigned char 类型(因此每个可能的对象表示都是有效的并且具有不同的值)。”

  • <ctype.h> "在所有情况下,参数都是int,其值应可表示为unsigned char 或应等于宏EOF 的值。

【讨论】:

  • “C 库将 char 功能“视为” unsigned char 。”编译器仍然警告我 ``` 预期的 'char *' 但参数的类型是 'unsigned char * ```。由于 unsigned char 显然不是我所期望的,我理解这是因为函数需要一个有符号变量。这是一个错误的结论吗?
  • @DieterVansteenwegenON4DD char 不是签名类型。它是与 signed charunsigned char 不同的类型,可以是有符号或无符号的,这并不重要,因为您使用它来表示字符,而不是整数值
  • @DieterVansteenwegenON4DD “我已经开始使用 uint8_t 数组来保存字符串”。在 C 中,string 是“...是一个连续的字符序列,由第一个空字符终止并包括第一个空字符。”。如果字符类型或您的字符串是signed charunsigned charchar,则标准字符串函数的执行方式相同。 (好像“unsigned char”),但该函数只能有 1 个签名。妥协是char。将char * 用于字符串,或者,不太可取的是,转换为(char*)
【解决方案2】:

到目前为止,我大多听说你应该在嵌入式设备上尽可能多地(尤其是?)使用无符号类型,

你听过这个的人解释过为什么吗?这种解释是基于可靠的分析和工程,还是凭空捏造的?

经验法则的问题在于,它们经常在错误的情况下被不假思索地应用。当你需要使用无符号类型时使用无符号类型,当你需要使用有符号类型时使用有符号类型。

我已经开始使用 uint8_t 数组来保存字符串了。

不要。这不是它的用途。

Plain char 可能是有符号或无符号的,具体取决于环境。 基本字符集(大写和小写拉丁字母、十进制数字和基本图形字符集)的字符编码总是非负数,但扩展字符可能有正或负编码。

6.2.5 类型
...
3 声明为<strong>char</strong> 类型的对象大到足以存储基本的任何成员 执行字符集。如果基本执行字符集的成员存储在 <strong>char</strong> 对象,其值保证为非负数。如果任何其他字符存储在 char 对象,结果值是实现定义的,但应在范围内 可以在该类型中表示的值。

C 2011 Online Draft

处理字符串的 C 库函数需要指向 char 的指针,而不是 unsigned charuint8_t 或其他任何东西。虽然对于任何提供它的平台来说,uint8_t 很可能只是unsigned char 的 typedef 名称,但这并不是保证。 char 必须至少 8 位宽,但有些平台可能更宽(其中一个旧 PDP 使用 9 位字节和 36 位字,并且取决于应用程序我可以看到一些特殊用途的嵌入式系统使用不稳定的尺寸)。

【讨论】:

  • 不要忘记 char 可能是 EBCDIC、ASCII、UTF8、UTF16 或...还必须关心他们想要的字符编码是否实际上与编译器使用的字符编码匹配。例如。如果您从网络获取原始字节并且必须将它们转换为“实现定义的字符”,那么...
  • “经验法则的问题在于,它们经常在错误的情况下被不假思索地应用。当你需要使用无符号类型时使用无符号类型,当你需要使用有符号类型时使用有符号类型。”是的,我宁愿先让新手代码正确,然后再问为什么。因为要花一些时间来解释所有隐式类型提升、与有符号类型上的按位运算符相关的所有小问题、有关为整数常量(dec 或 hex)挑选的编译器类型的所有细节、整个签名疯狂C 允许 1 的 compl + 有符号幅度。以此类推。
  • 我更愿意让新手按照他们所说的去做:“这是一个经验法则,在你了解得更清楚之前使用它”。如果他们使用批判性思维并质疑经验法则,那很好……但他们最终还是会使用它。
  • @Lundin 非常感谢。事实上,我试图做到正确,这导致在没有真正理解所有细节的情况下遵循建议。似乎结论是“尽可能使用无符号,但对于文本使用不带符号的字符”
  • @Lundin 我认为仅使用 unsigned 本质上并不安全,也不应该以这种方式教授它。无符号可能是位操作的正确选择,但如今嵌入式系统的作用远不止于此。如果一个不熟练的程序员被告知总是使用无符号,那么很快他们就会通过减去两个无符号数而意外产生溢出 ^^
【解决方案3】:

到目前为止,我大多听说你应该在嵌入式设备上尽可能多地使用无符号类型(尤其是?)

这主要是因为(意外或有意)带符号的操作数与位运算符混合会造成严重破坏。但在低级编程中,实际上需要使用有符号类型的情况并不多。

例如,MISRA-C 强制您始终使用无符号变量、操作数和整数常量除非意图实际使用有符号类型。所以这不仅仅是基于意见的东西,MISRA-C 是大多数专业嵌入式系统的事实上的行业标准。

所以我开始使用 uint8_t 数组来保存字符串

没关系,但为此目的使用char 也没有错。 唯一 可以使用char 的时间是您打算存储文本的时候。请注意,char 特别讨厌,因为与语言中的所有其他类型不同,它具有未知的符号。每个编译器都可以使 char 有符号或无符号,并且仍然符合 C 标准。所以依赖 char 被签名或未签名的代码被破坏了。但是,对于文本字符串,这无关紧要,因为它们始终是正数。

但是,如果我从 stdlib.h 调用 itoa,它需要一个指向有符号字符 (int8_t) 数组的指针:

您的编译器显然将char 视为已签名。首先请注意itoa 不是标准C 并且不允许存在于stdlib.h 中,当需要严格的C 标准一致性时。但更重要的是,不同的编译器可能会以不同的方式实现该功能,因为它不是标准化的。

事实证明,您可以安全地在各种字符类型之间随意转换:charunsigned charsigned charint8_tuint8_t(stdint.h 8 位类型几乎已死肯定是字符类型,即使标准没有明确说明)。字符类型具体有各种与之相关的特殊规则,这意味着您始终可以将某些内容转换为字符类型。

只要不存在限定符(const 等),您就可以安全地将 uint8_t 数组转换为 char*

【讨论】:

  • 好的,我了解大部分内容(尽管不是很深)。我不明白为什么一个包含字符的变量可能需要一个负数,所以逻辑是使用无符号的。我在 SO 上问的另一个问题得到的答复是,除非你真的比编译器更了解正在发生的事情,否则强制转换确实应该避免,所以我试图避免这样做,并将变量严格定义为有符号或无符号。此外,有人建议我在嵌入式系统上使用 stdint.h,因此选择了 uint8_t。从现在开始,我将使用 char(无符号/无符号)作为文本...
  • @DieterVansteenwegenON4DD “除非你真的比编译器更了解,否则强制转换是应该避免的”这是一个合理的规则,但在这种特定情况下,我们碰巧比编译器更了解 ;) @987654337 @ 和 uint8_t* 不一定是指向兼容类型的指针,所以编译器是正确的。但是,字符类型特别具有特殊规则,允许存储在字符类型中的数据安全地转换为不同的字符类型。
  • (“特殊规则”是无趣的语言律师,如“无填充位”、“无陷阱表示”、兼容的“有效类型”,以及从指针到指针时的特殊指针转换规则-object 指向字符的指针。)
  • @DieterVansteenwegenON4DD 使用uint8_t 和stdint.h 嵌入式系统的正确选择。我也时不时将它用于文本,但 char 对于文本来说往往更轻松,因为它不会像你在这里得到的那种工具产生警告。
  • 谢谢@chux,这就是我所缺少的!
【解决方案4】:

到目前为止,我大多听说你应该尽可能多地使用无符号类型 尽可能

首先——这根本不是事实——你应该使用正确的类型What is the correct type? 这是最适合您需求的类型。 How can I know which type is best for me? 这取决于你用它做什么。它应该有一个类型来存储您的程序可能想要存储的所有可能值。

所以你不应该再听这个人了。

【讨论】:

  • 那么,为什么要使用带符号的 8 位变量而不是无符号的变量来将字符存储在数组中?在这里尝试学习,“常识”是没有知识是不可能的......也许我误解了或者那个人只是说特定的用例......
  • 如果您正在处理不一定代表字符串的数据。例如,从另一个设备接收数据的字节缓冲区。有时它可能有字符串数据,但有时它可能有原始字节。在这种情况下,我会将其保留为 uint8_t* ,并在我知道它将包含我期望的数据时将其转换为 char*。
  • @DieterVansteenwegenON4DD 使用 char 数组,该数组由一系列字节组成,不关心其符号。只需让编译器选择合适的即可。例如unsigned char is more efficient in ARM,因此char 在该架构上通常是未签名的
  • 当你意外得到签名操作数时会弹出签名类型的问题,这是由隐式提升等引起的。除非明确需要,否则避免签名类型几乎是嵌入式行业标准。
猜你喜欢
  • 1970-01-01
  • 2010-09-09
  • 1970-01-01
  • 1970-01-01
  • 2010-09-30
  • 1970-01-01
  • 1970-01-01
  • 2015-12-27
相关资源
最近更新 更多