使用gcc时,是什么原因导致char被签名或取消签名?

时间:2017-09-28 07:12:33

标签: c gcc

如果C中的char(使用gcc)是签名还是未签名,会导致什么?我知道标准并没有规定另一个标准,我可以从limits.h检查CHAR_MINCHAR_MAX但是我想知道在使用gcc时触发一个是什么< / p>

如果我从libgcc-6读取limits.h,我发现有一个宏__CHAR_UNSIGNED__定义了一个&#34;默认&#34; 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)
  • Intel(R)Core(TM)i3-4150
  • libgcc-6-dev:6.3.0-18
  • char已签名

Raspberry Pi3

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

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

6 个答案:

答案 0 :(得分:52)

根据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)来帮助您。

BTW gcc -dM -E -x c /dev/null也提供__BYTE_ORDER__等,如果你想要一个无符号的8位字节,你应该使用<stdint.h>及其uint8_t(更便携,更可读)。标准limits.h定义CHAR_MINSCHAR_MIN以及CHAR_MAXSCHAR_MAX(您可以将它们进行比较,以便检测signed char的实现),等等...

顺便说一下,你应该关心character encoding,但今天的大多数系统都使用UTF-8 everywhere。像libunistring这样的图书馆很有帮助。另请参阅this并记住,实际上Unicode中编码的UTF-8字符可以跨越几个字节(即char - s)。

答案 1 :(得分:41)

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

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

答案 2 :(得分:12)

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

根据this参考链接:

  

C和C ++标准允许字符类型char 签名或   未签名,具体取决于平台和编译器

     

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

     

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

     

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

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

答案 3 :(得分:7)

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

定义

其他平台将有类似的ABI标准文档,这些文档指定了允许不同C编译器在调用约定,结构布局和类似内容上彼此一致的规则。 (有关其他x86 ABI文档的链接或其他体系结构的其他位置,请参阅标记wiki。大多数非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的一些实现允许枚举大于一个   INT。底层类型碰到unsigned int,long int或   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在同一平台的不同编译器中表现不同,则它们实际上并不兼容。

答案 4 :(得分:6)

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

-funsigned-char
-fsigned-char

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

默认值取决于平台,并在构建gcc时固定。选择它是为了与该平台上存在的其他工具的最佳兼容性。

Source

答案 5 :(得分:1)

一个重要的实际注意事项是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之间的所有数字。