int8_t和uint8_t是否旨在用作char类型?

64

给定这个C++11程序,我应该期望看到一个数字还是一个字母?或者不要有期望?

#include <cstdint>
#include <iostream>

int main()
{
    int8_t i = 65;
    std::cout << i;
}

标准是否规定了该类型可以或将成为字符类型?


整数类型根据规格书必须至少为16位。 - stdcall
2
uint8_t 是一种整数类型,而不是字符类型。我期望的是数字,而不是字母。这似乎又是C++委员会的一个错误(GCC 6.3.1-1将它们打印为字符)。委员会在 std::byte 方面做得部分正确。std::byte 不会打印为字符类型(目前它根本不打印。希望将来能够修复)。 - jww
1
uint8_t是一种整数类型,@jww,毫无疑问。问题只是所有字符类型(以及bool)也是整数类型,这既有好处也有坏处。(在这种情况下,坏处是编译器不够聪明,无法跟踪类型别名以确定预期的用例。) - Justin Time - Reinstate Monica
5个回答

28
根据C++0x FDIS(N3290)的§ 18.4.1 [cstdint.syn],int8_t是一个可选的typedef,其规定如下:
namespace std {
  typedef signed integer type int8_t;  // optional
  //...
} // namespace std

§ 3.9.1 [basic.fundamental] 规定:

有五种 标准有符号整数类型:“signed char”、“short int”、“int”、“long int”和“long long int”。在此列表中,每个类型提供的存储空间至少与列表中前面的类型相同。还可能存在实现定义的 扩展有符号整数类型。标准和扩展的有符号整数类型统称为 有符号整数类型

...

boolcharchar16_tchar32_twchar_t 和有符号和无符号整数类型共同称为 整数类型。整数类型的同义词是 整型

§ 3.9.1 还规定:

在任何特定的实现中,一个普通的char对象可以取相同的值作为一个signed char或一个unsigned char,哪一个是由实现定义的。
很容易得出结论,如果char对象采用的是有符号数值,那么int8_t可能是char的一个typedef。然而,这并不是情况,因为char不在signed integer types列表中(标准和可能扩展的有符号整数类型)。另请参见Stephan T. Lavavej's comments关于std::make_unsignedstd::make_signed的评论。
因此,要么int8_tsigned char的一个typedef,要么它是一个扩展的有符号整数类型,其对象占据完全8位的存储空间。
为了回答你的问题,你不应该做出任何假设。因为两种形式的函数 x.operator<<(y)operator<<(x,y) 都已经被定义,所以 § 13.5.3 [over.binary] 规定我们要参考 § 13.3.1.2 [over.match.oper] 来确定 std::cout << i 的解释。进一步地,§ 13.3.1.2 表明,实现将根据 § 13.3.2 和 § 13.3.3 中的候选函数集合进行选择。然后,我们需要查看 § 13.3.3.2 [over.ics.rank] 来确定:
  • 如果int8_tsigned char的精确匹配(即signed char的typedef),则调用template<class traits> basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>&, signed char)模板。
  • 否则,int8_t将提升为int,并调用basic_ostream<charT,traits>& operator<<(int n)成员函数。

对于uuint8_t对象的std::cout << u情况:

  • 如果uint8_tunsigned char完全匹配,则会调用template<class traits> basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>&, unsigned char) 模板。
  • 否则,由于int可以表示所有的uint8_t值,所以uint8_t将被提升为int,然后调用basic_ostream<charT,traits>& operator<<(int n)成员函数。

如果您总是想打印一个字符,最安全、最清晰的选择是:

std::cout << static_cast<signed char>(i);

如果您总是想打印一个数字:

std::cout << static_cast<int>(i);

“标准允许typedef char int8_t”:我认为这不是正确的,因为char是一种整数类型,但即使它有符号,它也不是有符号整数类型。请参阅我的帖子,以获得关于这个(相当令人困惑的)术语的(希望是正确的)解释。 - Cassio Neri
@CassioNeri:C++标准不能将char列入有符号整数类型无符号整数类型列表,因为标准允许char对象采用有符号或无符号值。因此,我不同意您的观点,即仅因为char未列在有符号整数类型列表中,这意味着char不是有符号整数类型,即使它采用有符号值,因为标准不能char包括在任一有符号整数类型无符号整数类型列表中。 - Daniel Trebbien
3
尽管你的推理对我有道理,但我仍然相信我所说的话。显然,Stephan T. Lavavej同意我的看法(链接:http://connect.microsoft.com/VisualStudio/feedback/details/764409/visual-studio-2012-c-std-make-unsigned):“虽然“char”必须具有与“signed char”或“unsigned char”中的一个相同的符号和范围(由实现定义),但“char”既不是带符号整数类型也不是无符号整数类型”。请参见Johannes Schaub - litb在此处的评论(链接:https://dev59.com/IGDVa4cB1Zd3GeqPdXCU)。 - Cassio Neri
1
@CassioNeri:我现在认为你是对的。感谢你找到了这两个参数。由于Stephan T. Lavavej写的所有内容都对我有意义,所以我认为std::make_signed<int8_t>::type必须与int8_t完全相同,因为int8_t被指定为signed integer type。因此,即使char对象采用有符号值,int8_t也不能是chartypedef - Daniel Trebbien

23

int8_t是一个精确的8位宽度(如果存在)。

可以为8位的预定义整数类型只有charunsigned charsigned char。而shortunsigned short必须至少为16位。

因此,int8_t必须是signed char或普通char(如果普通char是有符号的)的typedef。

如果要将int8_t值作为整数而不是字符打印,则可以将其明确转换为int

原则上,C++编译器可以定义一个8位的扩展整数类型(可能称为__int8之类的),并使int8_t成为其typedef。我能想到的唯一理由是避免使int8_t成为字符类型。我不知道有哪些C++编译器实际上这样做了。

在C99中引入了int8_t和扩展整数类型。对于C来说,在可用char类型的情况下,没有特别的理由定义一个8位的扩展整数类型。

更新:

我对这个结论并不完全满意。在C99中引入了int8_tuint8_t。在C中,它们是否是字符类型并不特别重要;对于没有真正区别的操作,它们都是小整数类型(即使是标准C中最低级别的字符输出例程putc()也将要打印的字符作为一个int参数)。如果定义了int8_tuint8_t,它们几乎肯定会被定义为字符类型,但字符类型只是小的整数类型。

C++为charsigned charunsigned char提供了特定的重载版本的operator<<,因此std::cout << 'A'std::cout << 65产生非常不同的输出。后来,C++采用了int8_tuint8_t,但以一种几乎肯定是字符类型的方式。对于大多数操作,这与C中一样无关紧要,但对于std::cout << ...,确实有所不同,因为这个:

uint8_t x = 65;
std::cout << x;

这段代码可能会输出字母A而不是数字65

如果您想要一致的行为,请添加强制类型转换:

uint8_t x = 65;
std::cout << int(x); // or static_cast<int>(x) if you prefer

我认为问题的根源在于语言中缺少非字符类型的非常窄的整数类型。

至于意图,我可以猜测委员会成员要么没有考虑这个问题,要么认为它不值得解决。可以说(而且我也会这样说)添加 [u]int*_t 类型到标准中的好处大于它们在使用 std::cout << ... 时有些奇怪的行为所带来的不便。


3
我尝试寻找有关short类型最小大小的参考资料(除了至少与signed char类型大小相同),但未能找到 - 请问您能提供一个参考资料吗? - Mark B
5
C++标准3.9.1:“有符号和无符号整数类型应满足C标准第5.2.4.2.1节中给出的约束条件。” C 5.2.4.2.1对<limits.h>设置了要求,包括 SHRT_MIN <= -32767SHRT_MAX >= +32767USHRT_MAX >= 65535 - Keith Thompson
3
请记住,实现可能会将int8_t typedef为非标准的实现定义类型(并且在那些使用16位char的少数平台上很可能如此)。我认为C++11标准在关于这些stdint.h类型应该如何在重载中解决方面缺少一些必要的澄清。我怀疑这些类型如何匹配以进行重载分辨可能是实现定义的。 - Michael Burr
8
如果char是16位,那么CHAR_BIT==16,并且字节的定义是16位。除了位域(bit fields)之外,你不能拥有小于1个字节的整数类型。因此,在这种情况下就没有int8_t。(如果还不确定,请考虑sizeof(int8_t)。) - Keith Thompson
2
@BenVoigt [over.ics.rank]/4: "标准转换序列按其等级排序:精确匹配比提升更好,而提升比转换更好。" 在这种情况下,提升将是[conv.prom] / 1,即从具有较低转换等级的类型提升到(unsigned) int。 转换将是[conv.integral] / 1,即转换为任何整数类型(包括char)。 只有当char == uint8_t时,最可行的函数应该是operator<< (char) AFAIK,否则是operator<< (int) - dyp
显示剩余14条评论

8

我将按相反的顺序回答你的问题。

标准是否指定此类型可以或将成为字符类型?

简短回答:在大多数流行平台上(Linux上的GCC / Intel / Clang和Windows上的Visual Studio),int8_tsigned char,但在其他平台上可能是其他东西。

长答案如下。

C++11标准的第18.4.1节提供了<cstdint>的概要,其中包括以下内容:

typedef signed integer type int8_t; //optional

同一节的后面一段话中,第2段说:

标头[<cstdint>]定义了所有函数、类型和宏,与C标准7.18相同。

其中C标准指的是根据1.1/2确定的C99:

C++是一种通用的编程语言,基于C编程语言描述的ISO/IEC 9899:1999《编程语言-C》(以下简称C标准)。因此,在C99标准的第7.18节中可以找到int8_t的定义。更确切地说,C99的第7.18.1.1节说:“typedef名称intN_t指定带有宽度N的有符号整数类型,没有填充位,并且具有二进制补码表示。因此,int8_t表示带有精确8位宽度的有符号整数类型。”另外,C99的第6.2.5/4节也提到:
有五种标准的带符号整数类型,分别称为signed char、short int、int、long int和long long int。(这些和其他类型可以用多种方式指定,如6.7.2所述。)还可能有实现定义的扩展带符号整数类型。标准和扩展的带符号整数类型总称为带符号整数类型。
最后,C99的5.2.4.2.1节对标准带符号整数类型施加了最小大小的限制。除了signed char之外,所有其他类型至少有16位长。
因此,int8_t要么是signed char,要么是8位长的扩展(非标准)带符号整数类型。
GNU C库和Visual Studio C库都将int8_t定义为signed char。Intel和Clang在Linux上也使用libc,因此同样适用于它们。因此,在最流行的平台上,int8_t是signed char。
在这个C++11程序中,我应该期望看到一个数字还是一个字母?还是不要有期望?

简短回答: 在大多数流行的平台上(Linux上的GCC/Intel/Clang和Windows上的Visual Studio),你肯定会看到字母“ A”。在其他平台上,您可能会看到65。(感谢DyP向我指出此问题。)

在接下来的部分中,所有参考都是针对C ++11标准(当前草案N3485)进行的。

第27.4.1节提供了<iostream>的概述,特别是它声明了cout
extern ostream cout;

现在,根据第27.7.1节,ostreambasic_ostream的模板特化的typedef

template <class charT, class traits = char_traits<charT> >
class basic_ostream;

typedef basic_ostream<char> ostream;

第27.7.3.6.4节提供了以下声明:

template<class traits>
basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>& out, signed char c);

如果int8_tsigned char,那么将调用这个重载。同一部分还指定了此调用的效果是打印字符(而不是数字)。
现在,让我们考虑int8_t是扩展有符号整数类型的情况。显然,标准没有为非标准类型指定operator<<()的重载,但由于提升和转换,所提供的重载之一可能接受调用。实际上,int至少有16位长,可以表示所有int8_t的值。然后4.5/1表明int8_t可以被提升int。另一方面,4.7/1和4.7/2表明int8_t可以被转换signed char。最后,13.3.3.1.1表明在重载决议期间提升优先于转换。因此,以下重载(在23.7.3.1中声明)会被调用。

将调用basic_ostream& basic_ostream::operator<<(int n);

这意味着,该代码将会被调用。

int8_t i = 65;
std::cout << i;

将会打印65

更新:

1. 根据DyP的评论进行了更正。

2. 关于int8_t可能是chartypedef,添加了以下注释。

正如上面所说,C99标准(引用了第6.2.5/4节)定义了5种标准有符号整数类型(char不在其中),并允许实现添加它们自己的非标准有符号整数类型。C++标准在第3.9.1/2节中加强了这个定义:

有五种标准的有符号整数类型:“signed char”、“short int”、“int”、“long int”和“long long int”[...]也可能有实现定义的扩展有符号整数类型。标准和扩展的有符号整数类型统称为有符号整数类型

稍后,在同一节中,第7段说:

类型 boolcharchar16_tchar32_twchar_t,以及带符号和无符号整型类型被统称为整型类型。整型类型的同义词是整数类型

因此,char 是一种整数类型,但是char 既不是有符号整数类型也不是无符号整数类型,而且第18.4.1节(如上所述)指出,当存在时,int8_t 是有符号整数类型的一个 typedef

可能令人困惑的是,根据实现方式,char 可以取与 signed char 相同的值。特别地,char 可能具有符号,但它仍然不是 signed char。这在第3.9.1/1节中明确说明:

[...] charsigned charunsigned char三种不同的类型。在特定实现中,普通的 char 对象可以取同样的值作为 signed charunsigned char 中的一个;具体是哪个由实现定义。

这也意味着,根据 3.9.1/2 的定义,char 不是 有符号整数类型。

3. 我承认我的解释和具体地说是“char 不是有符号整数类型也不是无符号整数类型”这句话有点争议。

为了加强我的论点,我想补充一下Stephan T. Lavavej在这里说了同样的话,而Johannes Schaub - litb也在这篇文章的评论中使用了相同的句子。

2
我认为如果int8_t != signed char,它不会编译失败,有以下两个原因:1)int8_t可以是一个char(与signed char不同的独立类型)。2)即使int8_t是扩展整数类型,它也将是一个整数类型,参见[basic.fundamental]/2+7。正如[conv.prom]/1所告诉我们的那样,它可以被提升为intunsigned int(因为int必须是> = char> = 8位)。另请参见Daniel的答案。 - dyp
我对 char 的状态并不完全清楚。它是一种 _整数类型_,但既不是 有符号 也不是 _无符号整数类型_。它可能是扩展整数类型的 typedef 吗? - dyp
我已经和丹尼尔解决了这个问题:[over.ics.rank] 表示,在计算最佳可行函数(重载)时,将优先选择积分提升 [conv.prom] 而不是积分转换 [conv.integral]。而将 int8_t 提升为 int 是完全可能的(int >= 16 bit);对于 uint8_tunsigned int 也是如此。因此,如果必须进行转换,它将被提升为 int,输出将是 65(或任何数字),而不是 A。此外,我仍然不确定 typedef extended_int char; typedef extended_int int8_t; 是否合法。 - dyp
1
“标准类型和扩展类型必须不同”。您能提供一个参考吗?我会很感激。uint8_t 不能被“提升”为 signed char,它只能被提升为 intunsigned int 4.5[conv.prom]/1;但是由于 C 规定 int >= 16 位,因此它只能被提升为 int。它可以被“转换”为 signed char,但在重载决议期间将优先进行提升 [over.ics.rank]。 - dyp
@DyP 你是对的:如果 uint_8 不是 signed char,那么它可以被提升为 int,转换为 signed char 并且在重载决议期间提升是更可取的。因此,将打印 65。我会尽快更新。关于标准和扩展类型不同的问题:这是由 [conv.rank]/1 隐含的。更具体地说,它说:“任何标准整数类型的等级都应大于具有相同大小的任何扩展整数类型的等级。” 如果标准整数类型是扩展整数类型的 typedef,则它们将具有相同的大小和等级。感谢您的精彩讨论。 - Cassio Neri
显示剩余3条评论

5
我手头的工作草案N3376在[cstdint.syn] § 18.4.1中明确规定,int类型通常是typedefs。
namespace std {
typedef signed integer type int8_t; // optional
typedef signed integer type int16_t; // optional
typedef signed integer type int32_t; // optional
typedef signed integer type int64_t; // optional
typedef signed integer type int_fast8_t;
typedef signed integer type int_fast16_t;
typedef signed integer type int_fast32_t;
typedef signed integer type int_fast64_t;
typedef signed integer type int_least8_t;
typedef signed integer type int_least16_t;
typedef signed integer type int_least32_t;
typedef signed integer type int_least64_t;
typedef signed integer type intmax_t;
typedef signed integer type intptr_t; // optional
typedef unsigned integer type uint8_t; // optional
typedef unsigned integer type uint16_t; // optional
typedef unsigned integer type uint32_t; // optional
typedef unsigned integer type uint64_t; // optional
typedef unsigned integer type uint_fast8_t;
typedef unsigned integer type uint_fast16_t;
typedef unsigned integer type uint_fast32_t;
typedef unsigned integer type uint_fast64_t;
typedef unsigned integer type uint_least8_t;
typedef unsigned integer type uint_least16_t;
typedef unsigned integer type uint_least32_t;
typedef unsigned integer type uint_least64_t;
typedef unsigned integer type uintmax_t;
typedef unsigned integer type uintptr_t; // optional
} // namespace std

由于唯一的要求是必须是8位,所以将typedef为char是可接受的。


-2

char/signed char/unsigned char 是三种不同的类型,而且 char 并不总是 8 位。在大多数平台上,它们都是 8 位整数,但是 std::ostream 只为像 >> 这样的行为定义了 char 版本,比如 scanf("%c", ...)


1
在定义 int8_t 的每个平台上,它们都恰好是8位。 - Ben Voigt
@BenVoigt 并不完全正确,<climits> 中的 CHAR_BIT 定义了一个 char 中有多少位。虽然我还没有见过 CHAR_BIT 值为 8 以外的平台。 - richselian
1
如果CHAR_BIT大于8,则平台上不存在int8_t。标准不允许CHAR_BIT小于8。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接