在使用gcc时,是什么原因导致一个字符是有符号的还是无符号的?

13z8s7eq  于 2023-04-19  发布在  其他
关注(0)|答案(7)|浏览(142)

如果C中的char(使用gcc)是有符号的或无符号的,是什么原因导致的?我知道标准并没有规定一个在另一个之上,我可以从limits. h中检查CHAR_MINCHAR_MAX,但我想知道在使用gcc时是什么触发了另一个
如果我从libgcc-6中读取limits.h,我看到有一个宏__CHAR_UNSIGNED__,它定义了一个“默认”字符,有符号或无符号,但我不确定这是否是编译器在构建时设置的。
我试图列出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(stretch)
  • gcc版本6.3.0 20170516(Debian 6.3.0-18)
  • 英特尔(R)酷睿(TM)i3-4150
  • libgcc-6-dev:6.3.0-18
  • char已签名

[***] Raspberry Pi 3公寓:

  • Raspbian GNU/Linux 9.1(stretch)
  • gcc版本6.3.0 20170516(Raspbian 6.3.0-18+ rpi 1)
  • ARMv7处理器版本4(v7 l)
  • libgcc-6-dev:6.3.0-18+rpi
  • char无符号

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

qnakjoqk

qnakjoqk1#

根据C11标准(读作n1570),char可以是signedunsigned(所以实际上有两种C语言)。
一些processorsinstruction set architecturesapplication binary interfaces支持signed字符(字节)类型(例如,因为它很好地Map到一些machine code指令),其他则支持unsigned类型。
gcc甚至有一些-fsigned-char-funsigned-char选项,你几乎不应该使用(因为改变它会破坏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_MIN以及CHAR_MAXSCHAR_MAX(您可以比较它们是否相等,以检测signed char的实现)等等。
顺便说一句,你应该关心character encoding,但今天大多数系统都使用UTF-8 everywhere。像libunistring这样的库很有帮助。另请参阅this并记住,实际上,用UTF-8编码的Unicode字符可以跨越几个字节(即char-s)。

e5nqia27

e5nqia272#

默认值取决于平台和本机代码集。例如,使用EBCDIC的机器(通常是大型机)必须使用unsigned char(或具有CHAR_BIT > 8),因为C标准要求基本代码集中的字符为正数,而EBCDIC使用240这样的代码来表示数字0。(C11标准,§6.2.5Types¶2说:* 声明为类型char的对象足够大,可以存储基本执行字符集的任何成员。如果基本执行字符集的成员存储在char对象中,则其值保证为非负。*)
您可以通过-fsigned-char-funsigned-char选项控制GCC使用哪个符号。这是否是一个好主意是一个单独的讨论。

fbcarpbf

fbcarpbf3#

字符类型charsignedunsigned,具体取决于平台和编译器。
根据this参考链接:
C和C++标准允许字符类型char为有符号无符号取决于平台和编译器
大多数系统,包括x86 GNU/Linux和Microsoft Windows,都使用带符号的char
但是那些基于PowerPC和ARM处理器的通常使用无符号字符
当在具有不同默认char类型的平台之间移植程序时,这可能会导致意外的结果。
GCC提供选项-fsigned-char-funsigned-char来设置默认类型char

dba5bblo

dba5bblo4#

至少在x86-64 Linux上,它由the x86-64 System V psABI定义
其他平台也会有类似的ABI标准文档,这些文档指定了一些规则,让不同的C编译器在调用约定、结构布局等方面达成一致。(请参阅x86标签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的一些实现允许枚举大于int。底层类型按顺序碰撞为无符号int、long int或无符号long int。

在这种情况下,char是否签名实际上直接影响调用约定,因为clang依赖于一个当前未记录的要求:根据被调用者原型,当作为函数参数传递时,窄类型是符号或零扩展到32位的。
因此对于int foo(char c) { return c; },clang将依赖于 caller 来对参数进行符号扩展(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在同一平台的不同编译器中表现不同,那么它们就不会真正兼容。

368yc8dk

368yc8dk5#

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

-funsigned-char
-fsigned-char

除非您确切地知道自己在做什么,否则不建议使用任何这些选项。
默认值是依赖于平台的,并且在构建gcc本身时是固定的。选择它是为了与该平台上存在的其他工具具有最佳兼容性。
Source

pvcm50d1

pvcm50d16#

一个重要的实际注意事项是,UTF-8字符串文字的类型,如u8"...",是char的数组,并且必须以UTF-8格式存储。基本集中的字符保证等效于正整数。然而,
如果任何其他字符存储在char对象中,则结果值是实现定义的,但应在该类型中可以表示的值范围内。
(In C++中,UTF-8字符串常量的类型是const char [],并且根本没有指定基本集之外的字符是否具有数值表示。
因此,如果你的程序需要旋转UTF-8字符串的位,你需要使用unsigned char。否则,任何检查UTF-8字符串的字节是否在某个范围内的代码都是不可移植的。
最好显式强制转换为unsigned char*,而不是编写char,并期望程序员使用正确的设置编译以将其配置为unsigned char。但是,您可以使用static_assert()来测试char的范围是否包括从0到255的所有数字。

qgelzfjb

qgelzfjb7#

https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
CHAR_未签名
GCC定义此宏当且仅当数据类型char在〉目标机器上是无符号的。它的存在是为了使标准头文件limits.h正确工作〉。您不应该自己使用此宏;请参考limits. h中定义的标准宏。
所以看起来你没有在列表中看到它的原因是你在一个系统上测试,char是有符号的,而宏在这样的系统上根本没有定义。我已经确认它确实出现在我的一个arm系统的cc -dM -E -x c /dev/null | grep -i CHAR的输出中。
C标准把它留给了实现,当然这并没有说太多,因为“实现”把一堆东西混在一起,编译器,操作系统,CPU架构等。
在Linux上,它取决于CPU家族。对于某些架构,有或曾经有很好的理由。例如,早期的arm没有真实的支持有符号字节。对于其他人来说,它似乎更随意,可能是从运行在同一硬件上的其他操作系统复制的。
Afaict windows和mac OS在所有目标体系结构上(或者至少在当前支持的所有体系结构上)都使用签名字符。

相关问题