关于在char变量中存储unsigned char值时的类型安全性

时间:2013-07-25 19:33:36

标签: c casting type-conversion language-lawyer type-safety

我有一个包含多个字符的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 charchar之间进行转换,而不会丢失精度并且不会冒未定义(或实现定义)行为的风险吗?

第6.2.5节第15段:

  

实施应将 char 定义为具有相同的范围,   表示和行为为 signed char unsigned char

第6.3.1.3节第3段:

  

否则,新类型已签名且值无法在其中表示;结果是实现定义的,或者引发实现定义的信号。

我担心如果char被定义为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),具有较小整数转换等级的类型的值范围是a   另一种类型的值的子范围。

第6.3.1节第2段:

  

如果 int 可以代表原始类型的所有值,则该值会转换为 int ;否则,它会转换为 unsigned int

第6.3.1.8节第1段:

  

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

char的范围保证与signed charunsigned char的范围相同,分别是intunsigned int的子范围由于它们的整数转换率较小。

由于整数促销规则要求在评估之前将charsigned charunsigned char提升至至少int,这是否意味着{{1}在比较中可以保持其“签名”吗?

例如:

char
  • signed char foo = -1; unsigned char bar = 255; if(foo == bar){ printf("same\n"); } 评估为假值,即使foo == bar在使用显式-1演员表时等同于255,也是如此?

更新

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

  

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

  • 这是否意味着即使是明确的转换也不安全?

例如,以下代码可能导致实现定义的行为,因为(unsigned char)可以定义为有符号整数类型

char

4 个答案:

答案 0 :(得分:1)

“这是否意味着char在整个比较过程中可以保持'签名'?”是;作为-1的{​​{1}}将被提升为signed char,其将保留其signed int值。对于-1,它在升级时也会保留其unsigned char值,所以是的,比较将为false。如果您希望它评估为true,则需要显式转换。

答案 1 :(得分:1)

它与如何存储char的内存有关,在unsigned char中,所有8位用于表示char的值,而signed 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,而无符号的tinychar将变为7.您还可以看到无符号7和带符号-1 tinychar之间的等价(==),因为两者的位相同(111)。

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

答案 2 :(得分:1)

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


答案1

<强>问题

  
      
  • 这种比较类型是否安全?
  •   

在这种特殊情况下,myarr[2]foobar之间的比较是安全的,因为两个变量都包含无符号值。但总的来说,情况并非如此。

例如,假设某个实现将char定义为与signed char具有相同的行为,int能够表示unsigned char和{{1}可表示的所有值}}

signed char

虽然char foo = -25; unsigned char bar = foo; if(foo == bar){ printf("This line of text will not be printed.\n"); } 设置为bar,但C99标准保证从foo转换为signed char时不会失去精确度(请参阅回答2 ),unsigned char条件表达式将评估 false

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

  

如果 foo == bar 可以代表原始类型的所有值,则该值会转换为 int ;否则,它会转换为 int

由于在此实施中unsigned int可以代表intsigned char的所有值,因此unsigned charfoo的值都会转换为{在评估之前{1}}因此,生成的条件表达式为bar,其计算结果为 false


答案2

<强>问题

  
      
  • 尽管如此,我可以安全地在int-25 == 231之间进行转换,而不会丢失精度并且不会冒未定义(或实现定义)行为的风险吗?
  •   

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

C99标准提供了一些保证,使我们能够安全地从unsigned char转换为char

第6.2.5节第15段:

  

实施应将 char 定义为具有相同的范围,   表示和行为为 unsigned char char

在此,我们保证signed char具有与unsigned char相同的范围表示行为 }或char。如果实施选择signed char选项,则从unsigned charunsigned char的转换基本上是charunsigned char的转换 - 因此没有宽度和信息是失去了,没有问题。

unsigned char选项的转换并不直观,但隐含地保证保持精确度。

第6.2.5节第6段:

  

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

6.2.6.1 第3段:

  

存储在无符号位字段和 signed char 类型的对象中的值应为   用纯二进制表示法表示。

第6.2.6.2节第2段:

  

对于有符号整数类型,对象表示的位应分为三个   groups:值位,填充位和符号位。不需要任何填充位;应该只有一个符号位。作为值位的每个位应具有相同的值   相应的无符号类型的对象表示中的相同位(如果有的话)   签名类型中的 M 值位和无符号类型中的 N ,然后 M unsigned N )。

  1. 首先,unsigned char保证与<=占用的存储量相同,与未签名对应的所有有符号整数相同。
  2. 其次,signed char保证具有纯二进制表示(即没有填充位且没有符号位)。
  3. unsigned char要求只有一个符号位,且不得超过与unsigned char相同的值位数。
  4. 鉴于这三个事实,我们可以通过pigeonhole principle证明signed char类型最多少一个,而不是unsigned char的值位数类型。同样,signed char可以安全地转换为unsigned char,不仅不会损失精度,也不会丢失 width 或信息:< / p>

    • signed char的存储空间大小为 unsigned char 位。
      • unsigned char必须具有 N 位的相同存储大小。
    • N没有填充或符号位,因此 signed char 值位
    • 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 char定义为char,则char变量可能包含超出signed char可表示的值范围的值。在这种情况下,结果是实现定义的实现定义的信号被引发。

    第6.3.1.3节第3段:

      

    否则,新类型已签名且值无法在其中表示;结果是实现定义的,或者引发实现定义的信号。


    答案3

    <强>问题

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

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

    unsigned

    第6.3.1.3节第3段:

      

    否则,新类型已签名且值无法在其中表示;结果是实现定义的,或者引发实现定义的信号。

    实现定义的结果将是在新signed类型可表示的值范围内返回的任何值。理论上,对于这些情况,实现可以一致地返回相同的值(例如unsigned foo = UINT_MAX; signed bar = foo; /* possible implementation-defined behavior */ ),从而发生丢失信息 - 即无法保证从signed转换为42再返回{ {1}}会产生相同的原始unsigned值。

    实现定义的信号符合C99标准 7.14节中规定的规则;允许实现定义C99标准未明确列举的其他符合信号。

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


    答案4

    <强>问题

      
        
    • SIGTERM评估为假值,即使unsigned在使用显式(signed)强制转换时等同于foo == bar
    •   

    请考虑以下代码:

    -1

    虽然在评估条件表达式之前,255unsigned char值被提升为至少signed char foo = -1; unsigned char bar = 255; if((unsigned char)foo == bar){ printf("same\n"); } ,但显式signed char强制转换将转换为unsigned char整数促销发生之前,值为int。此外,转换为unsigned char值在C99标准中已明确定义,并且不会导致实现定义的行为

    第6.3.1.3节第2段:

      

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

    这条件表达式基本上变为signed char,其计算结果为 true 。 直到该值在新类型的范围内。


    回答5

    <强>问题

      
        
    • 这是否意味着即使是明确的转换也不安全?
    •   

    通常情况下,unsigned charunsigned所代表的值范围之外的值的显式强制转换可能会导致实现定义的行为(参见< strong>回答3 )。对于适用于C99标准的第6.3.1.3节第3段,无需隐含转换。

答案 3 :(得分:0)

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

我在使用signed char类型进行缓冲操作方面经验不佳。那样你的问题就会发生。然后确保在编译期间打开了所有警告并尝试修复它们。

相关问题