C/C++那些事儿之 数的转换

华宰 发布于 2012/03/29 22:12
阅读 827
收藏 2
问题: d=1,-d==?
我们看看答案会是什么样的:
 -----------------------------
下面的代码的输出是什么?

int main() {
  char  dt = '\1';
  long tdt;
  tdt = -dt;
  printf("%ld\n", tdt);
}
我的第一反应是这个输出应该是”-1“。 我想你也是这样认为的。然而如果在64位系统上输出是什么呢?我期望也是”-1“。 我们看看是这样的吗。

让我们测试一下。
#xlc -q64 a.c  -qlanglvl=extended
#./a.out
4294967295
!! 这里的输出是“4294967295” 而不是“-1”,怎么会这样呢?
别急别急,可能是我们漏了什么?  
在回答之前上面问题之前,我们看看下面代码段:
"char  dt = '\1'; -dt;"
对于这个代码段,我们确认一下dt变量是有符号还是没有符号的?C
我们看看C语言标准怎么说的:
The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char.
也就是说标准没有对char类型变量的符号做要求。我们在看看XL编译器的文档http://publib.boulder.ibm.com/infocenter/comphelp/v111v131/topic/com.ibm.xlc111.aix.doc/language_ref/ch.html
里面说:
By default, char behaves like an unsigned char. To change this default, you can use the -qchars option or the #pragma chars directive. See -qchars for more information.
看来XL编译器中,char默认是无符号的。我们可以用-qchars来改变这个默认行为。我们来测试一下:
#xlc -q64 a.c  -qlanglvl=extended -qchar=signed
#./a.out
-1
太好了,原来如此。我们好像明白了。
不要急,现在都讲究Hold住。你真的明白了吗?可能还没有。 我们看另一个问题:
不用-qlanglvl=extended也可以得到“-1”,不管用不用-qchars, 为什么呢?如下:
-------------
#xlc -q64 a.c
#./a.out
-1
--------------
并且,下面的代码也打印“-1”,即使使用-qlanglvl=extended:
--------------
int main() {
  unsigned char  dt = '\1';
  long tdt =dt;
  tdt = -tdt;
  printf("%ld\n", tdt);
}
#xlc -q64 a.c  -qlanglvl=extended ;./a.out
-1
--------------
为什么下面的代码的输出又成了 "4294967295"?
int main() {
  unsigned  dt = '\1';
  long tdt;
  tdt = -dt;
  printf("%ld\n", tdt);
}
这里面似乎有一些深层的东西,我们来仔细看看下面几点:
* 减号操作符的行为
* 整数提升的规则 ( integral promotion)
* 类型转换 (type cast)
########################################
我们一个一个来看:
1. 减号操作符的行为: ISO C++标准说
The result of the unary - operator is the negative of its (promoted) operand.  The integer promotions are performed on the operand, and the result has the promoted type.
因此减号操作符的运算结果类型依赖于操作数被提升之后的类型。
在XL编译器的文档中,也有类似的说法:
http://publib.boulder.ibm.com/infocenter/comphelp/v111v131/topic/com.ibm.xlc111.aix.doc/language_ref/artnege.html
Unary minus operator - :The result has the same type as the operand after integral promotion.
2. 整数提升的规则
在XL文档中我们看到下面的信息
http://publib.boulder.ibm.com/infocenter/comphelp/v111v131/topic/com.ibm.xlcpp111.aix.doc/language_ref/integer_float_promotion.html?resultof=%22%70%72%6f%6d%6f%74%65%64%22%20%22%70%72%6f%6d%6f%74%22%20
If an integer type other than wchar_t, bit field, and Boolean can be represented by the int type and its rank is lower than the rank of int, the integer type can be converted to the int type. Otherwise, the integer type can be converted to the unsigned int type.
同时我们注意到XL的下面说明:

*****************************************************************
-qupconv (C only)
Specifies whether the unsigned specification is preserved when integral promotions are performed.
Defaults
-qnoupconv for all language levels except classic or extended
-qupconv when the classic or extended language levels are in effect
***********************************************************
可以看出选项-qupconv(-qlanglvl=extended)影响了整数提升时对符号位的处理行为。指定-qlanglvl=extended或-qupconv时保留符号位进行整数提升。
3. 我们知道在强制类换类型是符号位取决于目标类型。
由此,我们回过头来看看上面的代码:
----------------------
int main() {
  unsigned char  dt = '\1';
  long tdt =dt;              //类型强制转换 tdt 的仍然是”1“
  tdt = -tdt;                    //结果类型仍然是 long, 因此结果是 -1
  printf("%ld\n", tdt);
}
--------------------------
对于
int main() {
  unsigned  dt = '\1';
  long tdt;
  tdt = -dt;    ///==等价于=> (long)( (unsigned int) (-((unsigned int)u)) )
  printf("%ld\n", tdt);
}
dt类型是 unsigned int ,不需要 (根据上面的第2条)提升, 因此 "-dt" 的类型仍然是"unsigned int".  所以-dt 的值就是"0xFFFFFFFF" (在二进制上看),然后强制转换成long类型并付给dt;因此 0x00000000FFFFFFFF的结果是4294967295。

对于
int main() {
  unsigned char  dt = '\1';
  long tdt;
  tdt = -dt; 
  printf("%ld\n", tdt);
}

 tdt = -dt;  unsigned char首先需要提升,提升成为"unsigned int" 如果-qupconv或-qlanglvl=extended;否则提升成“int”
因此-qupconv/-qlanglvl=extended时:tdt=-dt 等价于to "(long)( (unsigned int) (-((unsigned int)td)) )" 。从而结果是4294967295
在-qnoupconv(其他langlvl)时等价于"(long)( ( int) (-(( int)dt)) )" 结果是-1。
至此,我们终于能过回答“d=1,-d==?”了。其结果依赖于被操作数的类型,可能发生的的类型转换以及编译器选项。

文章出处:
https://www.ibm.com/developerworks/mydeveloperworks/blogs/12bb75c9-dfec-42f5-8b55-b669cc56ad76/entry/c_c__E9_82_A3_E4_BA_9B_E4_BA_8B_E5_84_BF_E4_B9_8B__E6_95_B0_E7_9A_84_E8_BD_AC_E6_8D_A211?lang=zh

加载中
0
中山野鬼
中山野鬼
这事说的有点复杂了。呵呵。我看了都有点晕了。
0
Yisen
Yisen
其实就是有无符号的问题
0
中山野鬼
中山野鬼
我觉得这个文章原创的人,可能对汇编和CPU内部构造了解的少了点。从底层硬件对C语言的实现方法上谈,说不定能更简单明了些。
0
小熊猫大暴走
小熊猫大暴走

char  dt = '\1';

没人会蛋疼的这样写吧?

0
中山野鬼
中山野鬼

又仔细看了一遍。发现原作者有个描述有失偏颇。不是我估计咬文嚼字。

转换和强制转换是两个操作。后者之明确标出操作类型。

转换是编译器,根据编译选项,在代码没有明确转换方式下,根据标准规则的一个自动操作行为,会受到编译选项的影响。但是强制转换是不会受到此影响。例如上文中的例子,改成下面的

int main() {
  unsigned char  dt = '\1';
  long tdt;
  tdt =(unsigend char)( -dt); //这里强制转换
  printf("%ld\n", tdt);
}

编译器选项怎么改,始终是正数。这才是强制转换的例子。

正是因为担心不同的硬件平台,不同的编译选项可能对不同位宽的数据主要是扩展情况产生不同的操作,所以代码设计时有必要进行强制转换。例如一般扩展会有两种情况带符号还是不带符号。如下32位的机器,此处不谈类似DSP的可并发操作。

char c = -1; //char为有符号的。
//假设c在寄存器a0里,则a0 = 0b1111 1111
//且不谈一些组合指令,假设扩展指令是必须分开来的,则
unsigned int i;
i = c ;
//则会有两个汇编操作, 假设i在寄存器a1里。存在一个临时寄存器a2
mov a1,a0 ; 此时a1 里面是  0b1111 1111
unexp8 a1,a1 ; 此时a1 里面是 0b0000 0011
而如果是强制转换,如  i = (unsigned int) c ;此时,就是如下
unexp8 a2,a0  ; 此时a2 里面是 0b0000 0011
//注意上面多了一个语句,是由于强制转换导致的。
mov a1,a2 ; 此时a1 是  0b0000 00 11
//当然我这里有点抬杠,实际上很多时候两种方式在汇编上看都一样操作。因为上述两件事情可以一个语句处理掉

很少有CPU专门针对8位,16位,32位独立构造加法器

因此无论是8,16,32位都是同样的加法器。只不过对于8,16位,会存在高位,特别符号位的额外判断。这也是为什么一些ARM的优化推荐,没有特殊必要下,建议都用32位的原因。说这个只是希望大家理解,正常CPU。

如果你用8位,实际存储时,高位仍然会存在,只不过会被看作或不看作符号位。如果你尝试以下良种方式赋值

unsigned char uc = -1;
char c = -1;
则对应的汇编会完全不同,假设都在a0寄存器里
mov a0,#0xff        ;针对 uc
mov a0,#0xffffffff ;针对c
这个值什么时候是0xff什么时候是0xffff ffff是编译器决定的。这属于转换,但不属于强制转换。

当然我说的这些只是针对我碰过的CPU。不代表所有CPU就是这样的。

 

 

返回顶部
顶部