如果C中的 char
(使用gcc)是签名还是未签名,会导致什么?我知道标准并没有指定一个,而且我可以从limits.h检查 CHAR_MIN
和 CHAR_MAX
但是我想知道在使用gcc时是什么触发了另一个
如果我从libgcc-6读取limits.h,我看到有一个宏 __CHAR_UNSIGNED__
定义了"default" char有符号或无符号,但我不确定这是否是由编译器在(他) Build 的时间设置的 .
我试图列出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__
背景:我有一些代码可以在两台不同的机器上编译:
Desktop PC:
-
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
已签名
Raspberry 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架构......
6 回答
根据C11标准(阅读n1570),
char
可以是signed
或unsigned
(所以你实际上有两种口味的C) . 究竟是什么具体的实施 .有些processors和instruction set architectures或application 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_MIN
和SCHAR_MIN
和CHAR_MAX
和SCHAR_MAX
(你可以比较它们的相等性以检测signed char
的实现)等等......顺便说一下,你应该关心character encoding,但今天的大多数系统都使用UTF-8 everywhere . 像libunistring这样的图书馆很有帮助 . 另请参阅this并记住,实际上UTF-8中编码的Unicode字符可以跨越几个字节(即
char
-s) .默认值取决于平台和本机代码集 . 例如,使用EBCDIC(通常是大型机)的机器必须使用
unsigned char
(或者具有CHAR_BIT > 8
),因为C标准要求基本代码集中的字符为正,而EBCDIC使用240代码来代表数字0.(C11标准,§6.2 . 5 Types ¶2说:声明为char
类型的对象足以存储基本执行字符集的任何成员 . 如果基本执行字符集的成员存储在char
对象中,则其值保证为非负值 . )您可以使用
-fsigned-char
或-funsigned-char
选项控制GCC使用的符号 . 这是一个好主意是一个单独的讨论 .字符类型
char
为signed
或unsigned
,具体取决于平台和编译器 .根据this参考链接:
GCC提供选项
-fsigned-char
和-funsigned-char
来设置char
的默认类型 .至少在x86-64 Linux上,由the x86-64 System V psABI定义
其他平台将具有类似的ABI标准文档,这些文档指定了允许不同C编译器在调用约定,结构布局和类似内容上彼此一致的规则 . (有关其他x86 ABI文档的链接或其他体系结构的其他位置,请参阅x86标记wiki . 大多数非x86体系结构只有一个或两个标准ABI . )
从x86-64SysV ABI:图3.1:标量类型
_Bool * 1 1布尔值
char 1 1签名字节
签名的char
unsigned char 1 1无符号字节
...
int 4 4签署了4byte
签名int
枚举***
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) .即使除了召唤大会外, C compilers have to agree so they compile inline functions in a .h the same way.
如果
(int)(char)x
在同一平台的不同编译器中表现不同,则它们实际上不兼容 .gcc有两个编译时选项来控制
char
的行为:除非您确切知道自己在做什么,否则不建议使用任何这些选项 .
默认值取决于平台,并在构建gcc时固定 . 选择它是为了与该平台上存在的其他工具的最佳兼容性 .
Source .
一个重要的实际注意事项是UTF-8字符串文字的类型(例如
u8"..."
)是char
的数组,并且必须以UTF-8格式存储 . 基本集中的字符保证等于正整数 . 然而,(在C中,UTF-8字符串常量的类型是
const char []
,并且未指定基本集外部的字符是否具有数字表示 . )因此,如果您的程序需要旋转UTF-8字符串的位,则需要使用
unsigned char
. 否则,检查UTF-8字符串的字节是否在某个范围内的任何代码都不可移植 .显式转换为
unsigned char*
比编写char
更好,并期望程序员使用正确的设置进行编译,将其配置为unsigned char
. 但是,您可以使用static_assert()
来测试char
的范围是否包括0到255之间的所有数字 .