首页 文章

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

提问于
浏览
48

如果C中的 char (使用gcc)是签名还是未签名,会导致什么?我知道标准并没有指定一个,而且我可以从limits.h检查 CHAR_MINCHAR_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 回答

  • 41

    根据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_MINCHAR_MAXSCHAR_MAX (你可以比较它们的相等性以检测 signed char 的实现)等等......

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

  • 1

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

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

  • 7

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

    根据this参考链接:

    C和C标准允许字符类型char有符号或无符号,具体取决于平台和编译器 . 大多数系统,包括x86 GNU / Linux和Microsoft Windows,都使用signed char,但基于PowerPC和ARM处理器的系统通常使用unsigned char . (29)当在具有不同类型默认值的平台之间移植程序时,这会导致意外结果char .

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

  • 12

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

    其他平台将具有类似的ABI标准文档,这些文档指定了允许不同C编译器在调用约定,结构布局和类似内容上彼此一致的规则 . (有关其他x86 ABI文档的链接或其他体系结构的其他位置,请参阅x86标记wiki . 大多数非x86体系结构只有一个或两个标准ABI . )

    从x86-64SysV ABI:图3.1:标量类型

    C sizeof Alignment AMD64
    (字节)架构

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

    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 compilers have to agree so they compile inline functions in a .h the same way.

    如果 (int)(char)x 在同一平台的不同编译器中表现不同,则它们实际上不兼容 .

  • 6

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

    -funsigned-char
    -fsigned-char
    

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

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

    Source .

  • 52

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

相关问题