什么原因导致字符在使用gcc时被签名或未签名?

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

如果我从libgcc-6中读取limits.h,我发现有一个macros__CHAR_UNSIGNED__ ,它定义了一个“default”char signed或unsigned,但是我不确定这是否是编译器在构build时设置的。

我试图列出GCC预定义的makros

 $ gcc -dM -E -xc /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 

但无法find__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被签名

树莓Pi3

  • Raspbian GNU / Linux 9.1(伸展)
  • gcc版本6.3.0 20170516(Raspbian 6.3.0-18 + rpi1)
  • ARMv7处理器rev 4(v7l)
  • libgcc-6-dev:6.3.0-18 + rpi
  • char是无符号的

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

根据C11标准(阅读n1570 ), char可以被signedunsigned (所以你实际上有两种C)。 到底什么是具体实现。

一些处理器和指令集体系结构或应用程序二进制接口支持有signed字符(字节)types(例如,因为它很好地映射到某些机器代码指令),另一些有利于unsigned字符。

gcc甚至有一些-fsigned-char-funsigned-char 选项 ,除非你重新编译包括C标准库在内的所有东西,否则你几乎不应该使用这个选项 (因为它改变了它在调用约定和ABI时的一些特殊情况)。

您可以在Linux上使用feature_test_macros(7)和<endian.h> (请参阅endian(3) )或autoconf来检测系统的function。

在大多数情况下,你应该编写可移植的 C代码,这不依赖于这些东西。 你可以find跨平台的库(例如glib )来帮助你。

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

顺便说一句,你应该关心字符编码 ,但是现在大多数系统都在使用UTF-8 。 像libunistring这样的库是有帮助的。 另请参见这一点,并记住实际上用UTF-8编码的Unicode字符可以跨越几个字节(即char -s)。

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

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

字符型charsignedunsigned ,这取决于平台和编译器。

根据这个参考链接:

C和C ++标准允许字符types的字符有符号无符号这取决于平台和编译器

大多数系统(包括x86 GNU / Linux和Microsoft Windows)使用带符号的char

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

当在具有不同types的字符的不同默认值的平台之间移植程序时,这可能会导致意外的结果。

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

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

 -funsigned-char -fsigned-char 

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

默认是平台依赖的,并且在gcc本身被构build时被修复。 它被select为与该平台上存在的其他工具最佳兼容。

来源 。

至less在x86-64 Linux上,它是由x86-64 System V psABI定义的

其他平台将具有类似的ABI标准文档,这些文档指定了不同的C编译器在调用约定,结构布局和类似的东西方面相互认可的规则。 (请参阅x86标记维基链接到其他x86 ABI文档或其他体系结构的其他位置。大多数非x86体系结构只有一个或两个标准ABI。)

从x86-64 SysV ABI:图3.1:标量types

  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 -------------------------------------------------------------- ... 

*这种types在C ++中被称为bool

*** C ++和一些C的实现允许枚举大于int。 基础types按照该顺序碰撞到unsigned int,long int或unsigned long int。


char是否被签名实际上直接影响到调用约定,因为这是一个当前没有logging的要求,它依赖于: 当被作为函数parameter passing时 ,根据被调用者, 窄types被签名或者零扩展到32位原型。

所以对于int foo(char c) { return c; } int foo(char c) { return c; } ,clang将依靠调用者对arg进行签名扩展。 ( code + asm和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在同一平台的不同编译器中performance不同,那么它们将不兼容。

一个重要的实际注意事项是UTF-8string文字(如u8"..." )的types是char数组,必须以UTF-8格式存储。 基本集中的字符保证相当于正整数。 然而,

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

(在C ++中,UTF-8string常量的types是const char [] ,并且没有指定基本集以外的字符是否具有数字表示。)

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

最好是显式转换为unsigned char*不是写入char并希望程序员用正确的设置进行编译,将其configuration为unsigned char 。 但是,您可以使用static_assert()来testingchar的范围是否包含从0到255的所有数字。