【问题标题】:Is it safe to cast an unsigned char* to char*, and treat the dereferenced pointer as if it really points to a char?将 unsigned char* 转换为 char* 并将取消引用的指针视为它确实指向 char 是否安全?
【发布时间】:2014-06-04 08:17:34
【问题描述】:

在标题为 Warning generated due wrong strcmp parameter handling 的问题之后,似乎有一些关于标准实际上保证字符类型的值表示的问题。


问题

这看起来不错,但标准是否保证 (1) 将始终产生 true

char unsigned * p1 = ...;
char          * p2 = reinterpret_cast<char *> (p1);

*p1 == *p2; // (1)

【问题讨论】:

  • char const unsigned 确实是一种不寻常的类型命名方式。
  • 你为什么要使用unsigned char(保存一个字符而不是一个小整数)而不是char
  • @Walter 有些人发现将字节表示为范围 [0,255] 比范围 [-128,127] 更直观,包括我自己。此外,明确地显示某些内容是字节(无符号字符)而不是字符(字符)可能会很好。
  • @iFreilicht 同意。但我认为他想将其解释为一个字符而不是一个字节。
  • @walter 在显示为 char 和 bytes (%02X) 时有助于避免符号扩展问题。

标签: c++ c++11 language-lawyer c++14


【解决方案1】:

这可能会让您大吃一惊,

但在 C++11 标准 (N3337) 和即将推出的 C++14 (N3797) 中都没有这样的保证。

char unsigned * p1 = ...;
char          * p2 = reinterpret_cast<char *> (p1);

*p1 == *p2; // (1), not guaranteed to be true

注意charsigned 还是 unsigned 取决于具体实现; [basic.fundamental]p1



详情

标准保证每种字符类型都应;

  • 有相同的对齐要求
  • 占用相同数量的存储空间,并且;
  • 字符类型占用的所有存储位都应参与值表示,并且;
  • 值表示是一样的。

共享相同的存储量、对齐要求以及关于位参与的保证,意味着将引用一种类型 (unsigned char) 的左值转换为另一种类型(char), 是安全的.. 就实际演员而言。

3.9.1p1 基本类型 [basic.fundamental]

char 是否可以保存负值是实现定义的。字符可以显式声明为signedunsigned

一个char,一个signed char,和一个unsigned char占用相同的存储量并且具有相同的对齐要求(3.11);也就是说,它们具有相同的对象表示。对于字符类型,对象表示的所有位都参与值表示。

对于无符号字符类型,值表示的所有可能的位模式都表示数字。这些要求不适用于其他类型。

3.9p4 类型 [basic.types]

T 类型对象的 对象表示T, 类型对象占用的 Nunsigned char 对象的序列,其中@987654341 @ 等于 sizeof(T)。对象的值表示是一组保存T类型值的位。



那么,有什么问题?

如果我们将 unsigned char (UCHAR_MAX) 的最大值分配给 *p1 并且 *p2signed*p2 将不会是能够代表这个值。我们将溢出*p2,它很可能最终具有-1 的值。

注意有符号整数溢出实际上是未定义的行为


*p1 = UCHAR_MAX;

*p1 == *p2; // (1)

operator== 两边的类型必须相同才能进行比较,目前一侧是unsigned char,另一侧是char

因此编译器将求助于整体提升来找到一个可以表示两种类型的所有可能组合值的类型;在这种情况下,结果类型将为int

在整体提升之后,该语句在语义上等价于int (UCHAR_MAX) == int(-1),这当然是错误的。

【讨论】:

  • 嗯...我以为 char 已签名。
  • @FilipRoséen-refp char 不能有填充位(将查看标准所说的位置)。我不确定signed char 是否可以,但如果可以,那么提供signed char 填充位的实现必须使char 无符号。无论如何,strcmp 实际上并没有使用其参数的char 类型,而是在比较之前将它们转换回const unsigned char *
  • @FilipRoséen-refp 3.9.1p1:“对于字符类型,对象表示的所有位都参与值表示。”
  • @hvd 请注意,在最新草案中,只有 unsigned narrow chars 在使用包含不确定值时保证不会有未定义的行为。这向我表明只有 unsigned char 保证没有陷阱表示。
  • 轻微的挑剔,在问题中没有分配,也没有溢出。由于整数提升规则和 char 可以具有负值这一事实,这些值不相等。
【解决方案2】:
strcmp (buf1, reinterpret_cast<char const *> (buf2));

这看起来不错,

是的。 strcmp 采用 const char * 参数,但在内部将它们转换为 const unsigned char *(如果需要),因此即使 char 已签名并且两个不同的字节在将它们视为 char 时可以比较相等,它们仍然会比较使用strcmp 查看时会有所不同。

C99:

7.21 字符串处理&lt;string.h&gt;

7.21.1 字符串函数约定

3 对于本小节中的所有函数,每个字符都应被解释为具有unsigned char 类型(因此每个可能的对象表示都是有效的并且具有不同的值)。

也就是说,

但标准是否保证 (1) 将始终产生 true

char unsigned * p1 = ...;
char          * p2 = reinterpret_cast<char *> (p1);

*p1 == *p2; // (1)

您所写的内容无法保证。

采用一个常见的实现,带符号的char,使用二进制补码表示的 8 位字节。如果*p1UCHAR_MAX,那么*p2 == -1*p1 == *p2 将是错误的,因为升级到int 会赋予它们不同的值。

如果您的意思是 (char) *p1 == *p2*p1 == (unsigned char) *p2,那么这些仍然不能保证,因此您需要确保如果您从 char 的数组复制到 unsigned char 的数组,您不要包括这样的转换。

【讨论】:

    猜你喜欢
    • 2015-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 2011-10-19
    • 2013-09-23
    • 1970-01-01
    • 2023-03-08
    相关资源
    最近更新 更多