什么原因导致字符在使用gcc时被签名或未签名?
如果C(使用gcc)中的char
有符号或无符号,是什么原因造成的? 我知道这个标准并没有规定另一个,我可以从limits.h中检查CHAR_MIN
和CHAR_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
可以被signed
或unsigned
(所以你实际上有两种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_MIN
和SCHAR_MIN
, CHAR_MAX
和SCHAR_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表示: 声明为char
types的对象足够大,可以存储基本执行字符集的任何成员,如果基本执行字符集的成员存储在char
对象中,则其值保证为是非负的。 )
您可以使用-fsigned-char
或-funsigned-char
选项来控制GCC使用哪个符号。 这是不是一个好主意是一个单独的讨论。
字符型char
被signed
或unsigned
,这取决于平台和编译器。
根据这个参考链接:
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的所有数字。