【问题标题】:What causes a char to be signed or unsigned when using gcc?使用 gcc 时,是什么导致 char 有符号或无符号?
【发布时间】:2018-03-09 20:07:14
【问题描述】:

如果 C(使用 gcc)中的 char 是有符号或无符号的,会导致什么原因?我知道标准并没有规定另一个,我可以从limits.h中检查CHAR_MINCHAR_MAX,但我想知道使用gcc时是什么触发了另一个

如果我从 libgcc-6 中读取 limits.h,我看到有一个宏 __CHAR_UNSIGNED__ 定义了一个“默认”char 有符号或无符号,但我不确定这是否由编译器在(他的)构建时设置时间。

我试图列出 GCC 预定义的 makros 与

$ gcc -dM -E -x c /dev/null | grep -i CHAR
#define __UINT_LEAST8_TYPE__ unsigned char
#define __CHAR_BIT__ 8
#define __WCHAR_MAX__ 0x7fffffff
#define __GCC_ATOMIC_CHAR_LOCK_FREE 2
#define __GCC_ATOMIC_CHAR32_T_LOCK_FREE 2
#define __SCHAR_MAX__ 0x7f
#define __WCHAR_MIN__ (-__WCHAR_MAX__ - 1)
#define __UINT8_TYPE__ unsigned char
#define __INT8_TYPE__ signed char
#define __GCC_ATOMIC_WCHAR_T_LOCK_FREE 2
#define __CHAR16_TYPE__ short unsigned int
#define __INT_LEAST8_TYPE__ signed char
#define __WCHAR_TYPE__ int
#define __GCC_ATOMIC_CHAR16_T_LOCK_FREE 2
#define __SIZEOF_WCHAR_T__ 4
#define __INT_FAST8_TYPE__ signed char
#define __CHAR32_TYPE__ unsigned int
#define __UINT_FAST8_TYPE__ unsigned char

但找不到__CHAR_UNSIGNED__

背景:我有一些代码在两台不同的机器上编译:

台式电脑:

  • Debian GNU/Linux 9.1(延伸)
  • gcc 版本 6.3.0 20170516 (Debian 6.3.0-18)
  • 英特尔(R) Core(TM) i3-4150
  • libgcc-6-dev: 6.3.0-18
  • char 已签名

树莓派3

  • Raspbian GNU/Linux 9.1(拉伸)
  • gcc 版本 6.3.0 20170516 (Raspbian 6.3.0-18+rpi1)
  • ARMv7 处理器第 4 版 (v7l)
  • libgcc-6-dev: 6.3.0-18+rpi
  • char 未签名

所以唯一明显的区别是 CPU 架构...

【问题讨论】:

标签: c gcc


【解决方案1】:

根据C11 标准(阅读n1570),char 可以是signedunsigned(所以你实际上有两种C 风格)。究竟是什么是特定于实现的。

一些processorsinstruction set architecturesapplication binary interfaces 喜欢signed 字符(字节)类型(例如,因为它很好地映射到一些machine code 指令),其他人喜欢unsigned 之一。

gcc 甚至有一些 -fsigned-char-funsigned-char option,您几乎不应该使用它们(因为更改它会破坏 calling conventions 和 ABI 中的一些极端情况),除非您重新编译所有内容,包括您的 C standard library .

您可以在 Linux 上使用 feature_test_macros(7)<endian.h>(参见 endian(3))或 autoconf 来检测您的系统有什么。

在大多数情况下,你应该编写portable C 代码,它不依赖于那些东西。你可以找到跨平台库(例如glib)来帮助你。

顺便说一句,gcc -dM -E -x c /dev/null 也给出了__BYTE_ORDER__ 等,如果你想要一个无符号的 8 位字节,你应该使用<stdint.h> 和它的uint8_t(更便携,更易读)。而标准的limits.h 定义了CHAR_MINSCHAR_MINCHAR_MAXSCHAR_MAX(您可以比较它们的相等性以检测signed chars 的实现)等等...

顺便说一句,您应该关心character encoding,但当今大多数系统都使用UTF-8 everywherelibunistring 之类的库很有帮助。另请参阅this 并记住实际上以UTF-8 编码的Unicode 字符可以跨越多个字节(即char-s)。

【讨论】:

  • 当然,解决这个问题最简单、最便携的方法就是写你的意思:signed charunsigned,视情况而定。
【解决方案2】:

默认值取决于平台和本机代码集。例如,使用 EBCDIC(通常是大型机)的机器必须使用 unsigned char(或具有 CHAR_BIT > 8),因为 C 标准要求基本代码集中的字符为正数,而 EBCDIC 使用 240 之类的代码作为数字 0。(C11 标准, §6.2.5 Types ¶2 说:声明为类型 char 的对象大到足以存储基本执行字符集的任何成员。如果基本执行字符的成员set 存储在char 对象中,其值保证为非负数。)

您可以使用 -fsigned-char-funsigned-char 选项控制 GCC 使用的符号。这是否是一个好主意是一个单独的讨论。

【讨论】:

  • 当您像 OP 可能正在做的那样,在稍后在 RaspberryPi 上运行的 PC 上开发和测试软件时,这是一个好主意。
  • @luator 一个好主意是编写代码,这样char 是否有符号都无关紧要,当您需要有符号或无符号的 8 位值时使用 int8_tuint8_t .
  • 您能指出 C 标准在哪里规定基本代码集必须为正数吗?
  • @BlackJack 好的,我同意。
【解决方案3】:

gcc 有两个编译时选项来控制char 的行为:

-funsigned-char
-fsigned-char

除非您确切知道自己在做什么,否则不建议使用这些选项中的任何一个。

默认值是平台相关的,并且在 gcc 本身构建时固定。选择它是为了与该平台上存在的其他工具实现最佳兼容性。

Source.

【讨论】:

    【解决方案4】:

    字符类型 charsignedunsigned,具体取决于平台和编译器。

    根据this参考链接:

    C 和 C++ 标准允许字符类型 char 被 signed无符号取决于平台和编译器

    大多数系统,包括 x86 GNU/Linux 和 Microsoft Windows,都使用签名字符

    但是 基于 PowerPC 和 ARM 处理器的那些通常使用无符号 字符.(29)

    这可能会导致移植程序时出现意外结果 在 char 类型具有不同默认值的平台之间。

    GCC 提供-fsigned-char-funsigned-char 选项来设置char 的默认类型。

    【讨论】:

      【解决方案5】:

      至少在 x86-64 Linux 上,它由 the x86-64 System V psABI 定义

      其他平台将有类似的 ABI 标准文档,其中指定了让不同的 C 编译器在调用约定、结构布局和类似的东西上相互一致的规则。 (请参阅 标签 wiki 以获取其他 x86 ABI 文档的链接,或其他架构的其他位置。大多数非 x86 架构只有一两个标准 ABI。)

      来自 x86-64 SysV ABI:图 3.1:标量类型

         C            sizeof      Alignment       AMD64
                                  (bytes)         Architecture
      
      _Bool*          1             1              boolean
      -----------------------------------------------------------
      char            1             1              signed byte
      signed char
      ---------------------------------------------------------
      unsigned char   1             1              unsigned byte
      ----------------------------------------------------------
      ...
      -----------------------------------------------------------
      int             4             4              signed fourbyte
      signed int
      enum***
      -----------------------------------------------------------
      unsigned int    4             4              unsigned fourbyte
      --------------------------------------------------------------
      ...
      

      * 这种类型在 C++ 中称为bool

      *** C++ 和 C 的一些实现允许枚举大于 诠释。底层类型被撞到一个无符号整数、长整数或 unsigned long int,按此顺序。


      在这种情况下,char 是否已签名实际上会直接影响调用约定,因为根据被调用者原型,clang 依赖于一个当前未记录的要求:narrow types are sign or zero-extended to 32 bit when passed as function args

      所以对于int foo(char c) { return c; },clang 将依赖 调用者 对 arg 进行符号扩展。 (code + asm for this and a caller on Godbolt)。

      gcc:
          movsx   eax, dil       # sign-extend low byte of first arg reg into eax
          ret
      
      clang:
          mov     eax, edi       # copy whole 32-bit reg
          ret
      

      即使除了调用约定,C 编译器也必须同意,因此它们以相同的方式编译 .h 中的内联函数。

      如果(int)(char)x 在同一平台的不同编译器中表现不同,它们就不会真正兼容。

      【讨论】:

        【解决方案6】:

        一个重要的实用注意事项是,UTF-8 字符串字面量的类型,例如u8"...",是char 的数组,它必须以UTF-8 格式存储。基本集中的字符保证等价于正整数。然而,

        如果任何其他字符存储在 char 对象中,则结果值是实现定义的,但应在该类型可以表示的值范围内。

        (在C++中,UTF-8字符串常量的类型是const char [],根本没有指定基本集合之外的字符是否有数字表示。)

        因此,如果您的程序需要旋转 UTF-8 字符串的位,则需要使用 unsigned char。否则,任何检查 UTF-8 字符串的字节是否在一定范围内的代码都将不可移植。

        显式转换为unsigned char* 比编写char 并期望程序员使用正确的设置进行编译以将其配置为unsigned char 更好。但是,您可以使用static_assert() 来测试char 的范围是否包括从0 到255 的所有数字。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-12-27
          • 2022-01-03
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多