3

我有一个char包含几个字符的数组。我想将这些字符之一与unsigned char变量进行比较。例如:

char myarr = { 20, 14, 5, 6, 42 };
const unsigned char foobar = 133;

myarr[2] = foobar;

if(myarr[2] == foobar){
    printf("You win a shmoo!\n");
}

这种比较类型安全吗?

我从C99标准中知道charsigned charunsigned char是三种不同的类型(第 6.2.5 节第 14 段)。

  • 尽管如此,我能否安全地在unsigned charandchar和 back 之间进行转换,而不会丢失精度并且不会冒未定义(或实现定义)行为的风险?

第 6.2.5 节第 15 段中:

实现应定义为具有与orchar相同的范围、表示和行为。signed charunsigned char

第 6.3.1.3 节第 3 段中:

否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。

恐怕如果char定义为 a signed char,则myarr[2] = foobar可能导致实现定义的值无法正确转换回原始unsigned char值;例如,42无论unsigned涉及的值如何,实现都可能总是产生值。

  • unsigned这是否意味着将值存储signed在相同类型的变量中是不安全的?

还有什么是实现定义的信号;这是否意味着在这种情况下实现可以简单地结束程序?


第 6.3.1.1 节第 1 段中:

—— 等级long long int大于 等级long int大于 等级int大于 等级short int大于 等级signed char.

-- 任何无符号整数类型的等级应等于相应的有符号整数类型的等级,如果有的话。

第 6.2.5 节第 8 段中:

对于任何两个具有相同符号和不同整数转换等级的整数类型(见 6.3.1.1),具有较小整数转换等级的类型的值范围是另一个类型的值的子范围。

第 6.3.1 节第 2 段中:

如果 anint可以表示原始类型的所有值,则将该值转换为int; 否则,将其转换为unsigned int.

第 6.3.1.8 节第 1 段中:

否则,两个操作数都转换为与带符号整数类型的操作数类型对应的无符号整数类型。

的范围char保证与signed char或的范围相同unsigned char,它们都是 和 的子范围,int因为unsigned int它们的整数转换等级较小。

由于整数提升规则规定char,signed charunsigned char至少int在被评估之前被提升,这是否意味着char可以在整个比较过程中保持其“签名”?

例如:

signed char foo = -1;
unsigned char bar = 255;

if(foo == bar){
    printf("same\n");
}
  • 是否foo == bar评估为假值,即使-1等同于255使用显式强制转换(unsigned char)

更新

J.3.5 节第 1 段中,关于哪些情况会导致实现定义的值和行为:

-- 当值不能在该类型的对象中表示时,将整数转换为有符号整数类型的结果或引发的信号 (6.3.1.3)。

  • 这是否意味着即使是显式转换也不安全?

例如,以下代码是否会导致实现定义的行为,因为char可以定义为有符号整数类型

char blah = (char)255;
4

4 回答 4

2

我的原始帖子相当广泛,包含许多具体问题,我应该给每个问题单独的页面。但是,我在这里解决并回答每个问题,以便未来的访问者可以更轻松地了解答案。


答案 1

问题

  • 这种比较类型安全吗?

在这种特殊情况下,myarr[2]和之间的比较是安全的,因为两个变量都保存无符号值。foobar然而,总的来说,这是不正确的。

例如,假设一个实现定义char为具有与 相同的行为signed char,并且int能够表示由unsigned char和表示的所有值signed char

char foo = -25;
unsigned char bar = foo;

if(foo == bar){
    printf("This line of text will not be printed.\n");
}

虽然bar设置为等于foo,并且 C99 标准保证在从signed charto转换时不会损失精度unsigned char请参阅答案 2),但foo == bar条件表达式的计算结果为false

这是由于C99 标准第 6.3.1 节第 2 段所要求的整数提升的性质:

如果 anint可以表示原始类型的所有值,则将该值转换为int; 否则,将其转换为unsigned int.

由于在此实现int中可以表示 和 的所有值,因此signed charunsigned char的值在被评估之前foobar被转换为类型。int因此,结果条件表达式的-25 == 231计算结果为false


答案 2

问题

  • 尽管如此,我能否安全地在unsigned charandchar和 back 之间进行转换,而不会丢失精度并且不会冒未定义(或实现定义)行为的风险?

您可以安全地从charto转换而unsigned char不会丢失精度(宽度或信息),但在另一个方向转换 - unsigned charto char- 会导致实现定义的行为

C99 标准提供了某些保证,使我们能够安全地charunsigned char.

第 6.2.5 节第 15 段中:

实现应定义为具有与orchar相同的范围、表示和行为。signed charunsigned char

在这里,我们保证将具有与orchar相同的范围表示行为。如果实现选择了该选项,则从to的转换本质上是to的转换——因此不会丢失宽度或信息,也不会出现任何问题。signed charunsigned charunsigned charcharunsigned charunsigned charunsigned char

选项的转换signed char不是那么直观,但隐含地保证保持精度。

第 6.2.5 节第 6 段中:

对于每个有符号整数类型,都有一个对应的(但不同的)无符号整数类型(用关键字指定unsigned),它使用相同的存储量(包括符号信息)并具有相同的对齐要求。

6.2.6.1第 3 段中:

存储在无符号位域和类型对象中的值unsigned char应使用纯二进制表示法表示。

第 6.2.6.2 节第 2 段中:

对于有符号整数类型,对象表示的位应分为三组:值位、填充位和符号位。不需要任何填充位;应该只有一个符号位。作为值位的每个位应与相应无符号类型的对象表示中的相同位具有相同的值(如果有符号类型中有 M个值位,无符号类型中有N个,则M <= N)。

  1. 首先,signed char保证占用与 a 相同的存储量,unsigned char所有有符号整数相对于它们的无符号对应物也是如此。
  2. 其次,unsigned char保证具有纯二进制表示(即没有填充位和没有符号位)。
  3. signed char需要恰好有一个符号位,并且不超过与 相同数量的值位unsigned char

鉴于这三个事实,我们可以通过鸽巢原理证明signed char类型最多比值位的数量少一个unsigned char作为类型。类似地,signed char可以安全地转换为unsigned char不仅不损失精度,而且也不损失宽度或信息:

  • unsigned char具有N位的存储大小。
    • signed char必须具有相同的N位存储大小。
  • unsigned char没有填充或符号位,因此具有N值位
  • signed char最多可以有N非填充位,并且必须恰好分配一位作为符号位。
    • signed char最多可以有一个N-1值位和一个符号位

因此,所有位都与相应的值位signed char一对一地匹配;unsigned char换句话说,对于任何给定的signed char值,都有一个唯一的unsigned char表示。

/* binary representation prefix: 0b */
(signed char)(-25)   = 0b11100111
(unsigned char)(231) = 0b11100111

不幸的是,从unsigned charto转换char会导致实现定义的行为。例如,如果char由实现定义为signed char,则unsigned char变量可能保存的值超出了 a 可表示的值范围signed char。在这种情况下,结果要么是实现定义的,要么引发实现定义的信号

第 6.3.1.3 节第 3 段中:

否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。


答案 3

问题

  • unsigned这是否意味着将值存储signed在相同类型的变量中是不安全的?

如果无法在新类型中表示类型值,则尝试将unsigned类型值转换为signed类型值可能会导致实现定义的行为。unsignedsigned

unsigned foo = UINT_MAX;
signed bar = foo;    /* possible implementation-defined behavior */

第 6.3.1.3 节第 3 段中:

否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。

实现定义的结果signed将是在新类型可表示的值范围内返回的任何值。理论上,42对于这些情况,实现可以始终如一地返回相同的值(例如),因此会出现丢失信息——即不能保证从unsignedto转换signed回 tounsigned将导致相同的原始unsigned值。

实现定义的信号是符合C99 标准第 7.14 节规定的规则的信号;允许实现定义 C99 标准未明确列举的附加一致信号。

在这种特殊情况下,实现理论上可以SIGTERM发出请求终止程序的信号。因此,尝试将unsigned类型值转换为signed类型可能会导致程序终止。


答案 4

问题

  • 是否foo == bar评估为假值,即使-1等效于255使用显式 ( unsigned char) 强制转换?

考虑以下代码:

signed char foo = -1;
unsigned char bar = 255;

if((unsigned char)foo == bar){
    printf("same\n");
}

尽管signed charunsigned char值至少int在条件表达式的求值之前被提升,但显式unsigned char转换会将signed char值转换为unsigned char整数提升发生之前。此外,转换为unsigned值在 C99 标准中是明确定义的,不会导致实现定义的行为

第 6.3.1.3 节第 2 段中:

否则,如果新类型是无符号的,则通过在新类型中可以表示的最大值重复加或减一来转换值

这个条件表达式本质上变成255 = 255了计算结果为true。直到值在新类型的范围内。


答案 5

问题

  • 这是否意味着即使是显式转换也不安全?

一般来说,显式转换char为可表示的值范围之外的值signed char会导致实现定义的行为(请参阅答案 3)。C99 标准的第 6.3.1.3 节第 3 段无需隐式转换即可应用。

于 2013-08-08T20:22:35.683 回答
1

“这是否意味着它char可以在整个比较过程中保持其‘签名性’?” 是的; -1as asigned char将被提升为 a signed int,这将保留其-1价值。至于unsigned char,它在提升时也会保持它的255值,所以是的,比较是错误的。如果您希望它评估为 true,则需要显式转换。

于 2013-07-25T19:37:22.523 回答
1

它与 char 的内存如何存储有关,在 unsigned char 中,所有 8 位都用于表示 char 的值,而有符号 char 仅使用 7 位表示数字,第 8 位表示标志。

例如,让我们取一个更简单的 3 位值(我将把这个新的值类型称为 tinychar):

bits    unsigned  signed
000     0         0
001     1         1
010     2         2
011     3         3
100     4         -4
101     5         -3
110     6         -2
111     7         -1

通过查看此图表,您可以根据位的排列方式看到有符号和无符号 tinychar 之间的值差异。直到您开始进入负范围,这两种类型的值都是相同的。但是,一旦到达最左边的位变为 1 的点,该值突然变为带符号的负数。其工作方式是,如果您达到最大正值 (3),然后再添加一个,您最终会得到最大负值 (-4),如果您从 0 中减去 1,您将下溢并导致带符号的 tinychar 变为 - 1 而 unsigned tinychar 将变为 7。您还可以看到 unsigned 7 和有符号 -1 tinychar 之间的等价性 (==),因为两者的位相同 (111)。

现在,如果将其扩展为总共 8 位,您应该会看到类似的结果。

于 2013-07-25T20:00:22.333 回答
0

我已经测试了你的代码,它没有比较(signed char)-1(unsigned char)255相同。您应该首先将有符号字符转换为无符号字符,因为它在操作中不使用 MSB 符号位。

我对使用有符号字符类型进行缓冲区操作有不好的经验。像你的问题这样的事情就会发生。然后确保您在编译期间打开了所有警告并尝试修复它们。

于 2013-07-25T19:39:06.260 回答