void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) puts("> 6") : puts("<= 6");
}

C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是>6。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘
在汇编语言层面,声明变量的时候,没有 signed unsignde 之分,汇编器统统,将你输入的整数字面量当作有符号数处理成补码存入到计算机中,只有这一个标准!汇编器不会区分有符号还是无符号然后用两个标准来处理,它统统当作有符号的!并且统统汇编成补码!也就是说,
db -20 汇编后为:EC ,而 db 236 汇编后也为 EC 。这里有一个小问题,思考深入的朋友会发现,db 是分配一个字节,那么一个字节能表示的有符号整数范围是:-128 ~ +127 ,那么 db 236 超过了这一范围,怎么可以?是的,+236 的补码的确超出了一个字节的表示范围,那么拿两个字节(当然更多的字节更好了)是可以装下的,应为:00 EC,也就是说 +236的补码应该是00 EC,一个字节装不下,但是,别忘了截断这个概念,就是说最后汇编的结果被截断了,00 EC 是两个字节,被截断成 EC ,所以,这是个美丽的错误,为什么这么说?因为,当你把 236 当作无符号数时,它汇编后的结果正好也是 EC ,这下皆大欢喜了,虽然汇编器只用一个标准来处理,但是借用了截断这个美丽的错误后,得到的结果是符合两个标准的!也就是说,给你一个字节,你想输入有符号的数,比如 -20 那么汇编后的结果是符合有符号数的;如果你输入 236 那么你肯定当作无符号数来处理了(因为236不在一个字节能表示的有符号数的范围内啊),得到的结果是符合无符号数的。于是给大家一个错觉:汇编器有两套标准,会区分有符号和无符号,然后分别汇编。其实,你们被骗了。:-)

3.第一点说明汇编器只用一个方法把整数字面量汇编成真正的机器数。但并不是说计算机不区分有符号数和无符号数,相反,计算机对有符号和无符号数区分的十分清晰,因为计算机
进行某些同样功能的处理时有两套指令作为后备,这就是分别为有符号和无符号数准备的。但是,这里要强调一点,一个数到底是有符号数还是无符号数,计算机并不知道,这是由你来决定的,当你认为你要处理的数是有符号的,那么你就用那一套处理有符号数的指令,当你认为你要处理的数是无符号的,那就用处理无符号数的那一套指令。加减法只有一套指令,因为这一套指令同时适用于有符号和无符号。下面这些指令:mul div movzx … 是处理无符号数的,而这些:imul idiv movsx … 是处理有符号的。
举例来说:
内存里有 一个字节x 为:0x EC ,一个字节 y 为:0x 02 。当把xy当作有符号数来看时,x = -20 y = +2 。当作无符号数看时,x = 236 y = 2 c语言编译器怎么用?。下面进行加运算,用 add 指令,得到的结果为:0x EE ,那么这个 0x EE 当作有符号数就是:-18 ,无符号数就是 238 。所以,add 一个指令可以适用有符号和无符号两种情况。(呵呵,其实为什么要补码啊,就是为了这个呗,:-)
乘法运算就不行了,必须用两套指令,有符号的情况下用imul 得到的结果是:0x FF D8 就是 -40 。无符号的情况下用 mul ,得到:0x 01 D8 就是 472

4.为什么又扯到 c 了?因为大多数遇到有符号还是无符号问题的朋友,都是c里面的 signed unsigned 声明引起的,那为什么开头是从汇编讲起呢?因为我们现在用的c编译器,无论gcc 也好,vc6 cl 也好,都是将c语言代码编译成汇编语言代码,然后再用汇编器汇编成机器码的。搞清楚了汇编,就相当于从根本上明白了c,而且,用机器的思维去考虑问题,必须用汇编。(我一般遇到什么奇怪的c语言的问题都是把它编译成汇编来看。)

C 是可爱的,因为c符合kiss 原则,对机器的抽象程度刚刚好,让我们即提高了思维层面(比汇编的机器层面人性化多了),又不至于离机器太远(像c# java之类就太远了)。当初K&R 版的c就是高级一点的汇编,C又是可怕的,因为它把机器层面的所有的东西都反应了出来,像这个有没有符号的问题就是一例(java就不存在这个问题,因为它被设计成所有的整数都是有符号的)。为了说明它的可怕特举一例:

#include <stdio.h>
#include <string.h>

int main()
{
    int x = 2;
    char * str = "abcd";
    int y = (x - strlen(str) ) / 2;
 
    printf("%d\n",y);
}

结果应该是 -1 但是却得到:2147483647 。为什么?因为strlen的返回值,类型是size_t,也就是unsigned int ,与 int 混合计算时有符号类型被自动转换成了无符号类型,结果自然出乎意料。。。
观察编译后的代码,除法指令为 div ,意味无符号除法。
解决办法就是强制转换,变成 int y = (int)(x - strlen(str) ) / 2; 强制向有符号方向转换(编译器默认正好相反),这样一来,除法指令编译成 idiv 了。
我们知道,就是同样状态的两个内存单位,用有符号处理指令 imul idiv 等得到的结果,与用无符号处理指令muldiv等得到的结果,是截然不同的!所以牵扯到有符号无符号计算的问题,特别是存在讨厌的自动转换时,要倍加小心
为了避免这些错误,建议,凡是在运算的时候,确保你的变量都是 signed 的。
定义 INT16S  ltr1 = 0;
      ltr1 ++;
        if(ltr1 = = 32767)
        {
            ltr1 += 1;
            ltr1 += 2;
        }
  ltr1 等于32767时,到了有符号整数的最大值,加1就是-327680X8000
再加2就是-327660X8002
6.  INT16U i;
    INT16S *k;
    INT32S mbx;
    k = pmes + 7;
    mbx=0;
    for(i=0;i<COUNTOFCALSAMP;i++)    /*32*4计算的周波*/
    {   
        mbx += *k;
        k +=NUMOFCAL;        /*存放的是8个量*/
    }
    ltr = labs(mbx);  //整型量的值有可能出现负值时,当需要提取负值时需要用 labs取绝对值来反映其实际大小。
    ltr = (INT16U)(mbx);  //整型量的值有可能出现负值时,当mbx = - 6时,即mbx = 0xfffffffa,这种强制转换并不能得到我们想要的值,相反它的结果很大,是fffa
7.    INT16S ltr1;
    INT16U ltr2ltr3;
INT8U a;
    INT16S b;
    a=  6;
    b = -20;
    ltr1 = a + b;
    ltr2 = (INT16S)(a + b);
    c = 5;
    d = 15;
    ltr3 = c - d;        //这个值会得到515之间的距离。
结果是:ltr1 = -14  ltr2 = 65522  ltr3 = 65526
当一个整型变量赋值给一个无符号的整型时,数据格式不变,直接复制。
ltr1 本身是整型,ltr1 = a + b = 0xfff2 = - 14;
Ltr2 本身是无符号整型,ltr2 = (INT16S)(a + b) 0xfff2 =65522 ;可见它们的16进制数值是一样的,这取决于赋值号左边的数据类型,这个法则只适合加减运算。
Ltr3 当它是无符号整型时,值为655260xfff6),当它为整型时,值为-100xfff6);
16进制表示是一致的,区别是要看赋值号左边的数据类型。这个法则只适合加减运算
总结:
1.当运算符两边一个是有符号整型,一个是无符号整型时,有符号整型转成无符号整型 ,然后再运算。
这样问题就来了,当运算结果赋值给整型时是没有问题的,当运算结果赋值给无符号整型时,问题就有了,结果为负值的时候将得到一个很大数值,这个值用于运算时会出错。
可见,整型量与无符号整型在16进制的写法上没有什么区别,当整型量的负数部分从b1111 1111 1111 1111表示-1b1000 0000 0000 0000表示-32768,这样的好处是便于加减法运算,可以将减法运算转变为加法运算,如 7 - 3 = 4,将3变为补码为:b1111111111111101 + b111 = b100;可见这是一种技法。
8.  INT8U a;
    INT16S b;
INT16S ltr1;
INT16S ltr1;
INT16S acqbuf[256]
    b = -12368;
a = acqbuf[222]; 
    ltr1 = b/a; 
Ltr2 = b/a;
acqbuf[222] = 30 时,ltr1 = 1772ltr2 = 1772而不是我们要得到的负值。因为变量a是无符号整型,变量b的补码形式0xcfb0,运算时编译器将变量b当作无符号整型来运算,将最高位认为是数据,而不是符号,使用I$$UDIV函数来运算,即0xcfb0 / 30 = 1772
如果这样写ltr1 = b/acqbuf[222] = -412;就对了,因为他们都是整型量,编译器使用I$$DIV函数来运算,将最高位认为是符号位。
INT16U ltr1;
  INT16S ltr2;
  INT16S b;
INT16S acqbuf[256]
    b = -6;
    ltr1 = -6;    //ltr1是无符号整型 所有ltr1 = 65530
    ltr2 = acqbuf[224]/b;  //都为整型,且负数除以负数等于正数,所有ltr2 = 1
10  INT16S ltr1;
    INT16S ltr2;
    INT8U a,c,d;
    INT16S b;
INT16S acqbuf[256]
    a = 635;
    b = -635;
    ltr1 = b/acqbuf[222];        //acqbuf[222] = 29时,ltr1 = -21
    ltr2 = a/acqbuf[224];        //acqbuf[224] = -6时,ltr2 = 0
11.
INT16S ltr1;
INT16S ltr2;
INT16U ltr3;
INT16S acqbuf[256]
ltr1 = acqbuf[222]/-2;        //acqbuf[222] = 30时,ltr1 = -15
    ltr2 = acqbuf[224]/2;        //acqbuf[224] = -7时,ltr2 = -3
ltr3 = acqbuf[224]/2;        //acqbuf[224] = -7时,ltr2 = 0xfffd = 65533
以上除法编译的汇编语言中调用有符号的除法指令,I$$DIV函数。
再次总结:
混合运算时,加减乘除的表达式中,运算符两边一个是有符号整型,一个是无符号整型时,有符号整型转成无符号整型 ,然后再运算。
二进制运算以补码形式进行,不管加减乘除都会转化为这个形式,补码的好处就是将减法运算变为加法运算,这样的好处是可以省去硬件的减法电路。这样就出现了以下情况:在加减法运算时,处理器不区分变量是有符号的还是无符号的,全部以补码方式运算。
处理器的除法提供两种运算指令,一个是有符号的I$$DIV,一个是无符号的I$$UDIV,混合运算时就要慎重了,最好统一所有参与运算的变量,且都为有符号的整型变量,以免因转化带来不预期的运算结果。
那么乘法呢,来看一个有意思的问题
    INT8U a;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。