《深入理解计算机系统》笔记一:信息的表示与处理(2)——整数的表示

整数的表示

一、二进制补码(Two's Complement)

         几乎所有机器都是用二进制补码来表示有符号整数。书中有公式来说明,比较复杂,这里就不写了。简单来说,用最高位表示符号,0为正,1为负;负数的值,等于相应的正数的按位取反后,加1。

         例:+9的二进制补码表示为00001001,那么安位取反后为11110110,加1后为11110111,即-9

         反之,一个负数对应的正数也可以表示取反加1

         例:-9的二进制补码表示为11110111,取反后为00001000,加1得00001001

         二、二进制反码(Ones' Complement)

         二进制反码比较诡异,到目前为止我还不知道什么机器用反码表示负数。用最高位当作符号位,0为正,1为负。故而+9(00001001)的负数就表示为10001001,只将首位替换为1,其余位不变。

        三、为什么用二进制补码而不用二进制反码

     原因其实就是二进制反码不能一一对应,而二进制补码可以。比如,16的二进制表示为00010000,-8的二进制反码表示为10001000,而16+(-8)的值为10011000=-24,显然不对。所以如果用二进制反码表示负数的话,正数和负数的加法还得再定义一套规则,这就意味着需要用两套电路来实现加法。如果用二进制补码,-8表示为11111000,16+(-8)=100001000,最高一位溢出了,需要去掉,所以结果为00001000=8。

        注意,二进制补码的英文和二进制反码的英文,‘ 的位置不一样。

        为什么二进制补码可以精确的计算,这里有一篇文章,讲的很详细,也很优美,我上面的例子就是摘自此文章,有兴趣可以看一下

        《关于二进制补码》


         按位打印整数:

int show_bits( unsigned char *c, size_t len, char *str )
{
    #define BITS_PER_BYTE   8
    #define BITS_MASK       0x80U   //1000 0000
    
    int ii;
    int jj;
    unsigned char *p = c;
    unsigned char temp;
    
    if( NULL == c )
    {
        return -1;
    }

    if( NULL != str )
    {
        printf("%s\r\n", str );
    }

    /* 每个字节循环 */
    for( ii = 0; ii < len; ii++ )
    {
        /* 对字节中的每个比特循环 */
        for( jj = 0; jj < BITS_PER_BYTE; jj++ )
        {
            /* 只保留第jj  位,其余位置为0 */
            temp = *p & ( BITS_MASK >> jj );
            /* 将此位移到最低位,取此位的值 */
            temp = temp >> ( BITS_PER_BYTE - 1 - jj );
            printf("%d", temp );
        }
        printf(" " );
        p++;
    }

    printf("\r\n" );

    return 0;
}

    char a = 123;
    char b = -123;


    show_bits( (unsigned char*)&a, sizeof(a), "a:");
    show_bits( (unsigned char*)&b, sizeof(b), "b:");


    printf("a = %c, a = %d, a = %u, a = 0x%x\r\n", a, a, a, a );
    printf("b = %c, b = %d, b = %u, b = 0x%x\r\n", b, b, b, b );

输出的结果:

a:
01111011 ——123的二进制表示
b:

10000101 —— -123的二进制表示,将123的二进制表示取反,得出10000100,再加1,为10000101

a = {, a = 123, a = 123, a = 0x7b

b = ?, b = -123, b = 4294967173, b = 0xffffff85

        上面为123和-123对应的字符、有符号整数、无符号整数、十六进制的表示

注意:

1. 上面的输出中,-123的%u打印为4294967173,这是因为由于%u将10000101当作无符号数,所以打印出对应的无符号整数4294967173。同理,%x的打印为0xffffff85

2. %d,%u,%x均为整数的打印,%u打印时,由于要将一个负的char,当作无符号整数打印出来,所以打印之前会进行扩展,从一个字节扩展成4个字节。那么前面的三个字节是按照0填充还是按照1来填充呢?关于二进制补码的扩展,一律按照符号位来填充。

01111011 填充之后:00000000 00000000 00000000 01111011 = 123

10000101 填充之后:11111111 11111111 11111111 10000101 = 4294967173


再看一段代码:

    unsigned char a = 155;
    char b = 155;

    show_bits( (unsigned char*)&a, sizeof(a), "a:");
    show_bits( (unsigned char*)&b, sizeof(b), "b:");

    printf("a = %d, a = %u\r\n", a, a );
    printf("b = %d, b = %u\r\n", b, b );
输出:

a:10011011 

b:10011011 
a = 155, a = 155

b = -101, b = 4294967195

        这里,b为-101。b的二进制表示为10011011 ,而有符号整数的范围,为-128-127,155已经超出了此范围,所以%d将10011011解释为一个有符号数,为-101。


        从上面的输出中可以看出,虽然用%d打出的结果不同,但a和b在内存中的表示都是一样的,均为10011011。那么,能否直接用155与a和b进行判断呢?看下面的代码:

    unsigned char a = 155;
    char b = 155;

    show_bits( (unsigned char*)&a, sizeof(a), "a:");
    show_bits( (unsigned char*)&b, sizeof(b), "b:");

    if( 155 == a )
        printf("!!!a == 155\r\n");

    if( 155 == b )
        printf("!!!b == 155\r\n");

输出的结果:

a:
10011011 
b:
10011011 
!!!a == 155

        可以看出,if( 155 == b )这个条件根本没有进去。但b的值10011011 确实等于155啊,为什么会进不去呢?将可执行文件进行反汇编,得出的汇编代码如下:

    if( 155 == a )
 804853e:       0f b6 44 24 1f          movzbl 0x1f(%esp),%eax
 8048543:       3c 9b                   cmp    $0x9b,%al
 8048545:       75 0c                   jne    8048553 <main+0x60>
        printf("!!!a == 155\r\n");
 8048547:       c7 04 24 30 86 04 08    movl   $0x8048630,(%esp)
 804854e:       e8 01 fe ff ff          call   8048354 <puts@plt>

    if( 155 == b )
        printf("!!!b == 155\r\n");

    return 0;
 8048553:       b8 00 00 00 00          mov    $0x0,%eax

         可以看出,if( 155 == b )的这个条件判断,压根就没有被编译器生成汇编代码!也就是说,编译器认为b被定义为char类型,默认是有符号的,范围是-128-127,永远不可能等于155,所以干脆不将这条C语句翻译为汇编代码,也就是说这个条件永远进不来!我在工作中就遇到过此种情况,定义一个char类型的变量,然后对其赋值。如果有异常情况发生,将其赋为0xFF,然后再函数外面再做出错误处理,但由于编译器没有编译此条语句,所以永远不可能进行错误处理,这个问题当时百思不得其解,最后只得将类型改为unsigned char才解决


另外,此问题应该与编译器的优化细节相关,因为不可能所有编译器都会做这样的处理。我的编译器是gcc,从o1到o3,每个优化级别的汇编代码均没有这个判断。

万恶的是,编译器不认为这是一个bug,不会产生任何告警信息,只会默默的把我们写的错误代码优化掉,让我们陷入无穷的烦恼中。

更万恶的是,由于编译器对此条语句的处理是在优化时进行的,那么意味着前面的语法解析,词法解析等工作仍然会处理此语句,最初的汇编代码也还是会生成此

条语句的,只是在最后被干掉了。如果认为的在这个判断里加入一个错误,那么编译器是可以检查出来的,这反而更增加了我们排查的难度,所以以后遇到运算的操作,

首先应该想到的就是有没有溢出。


    if( 155 == b )
    {
        /* 我们看到的不是一个言行谨慎的大使,而是一个... */
        add a error
        printf("!!!b == 155\r\n");
    }


 
 
test.c: In function 'main':
test.c:60: error: 'add' undeclared (first use in this function)
test.c:60: error: (Each undeclared identifier is reported only once
test.c:60: error: for each function it appears in.)
test.c:60: error: expected ';' before 'a'

你可能感兴趣的:(优化,汇编,function,扩展,byte,编译器)