zoukankan      html  css  js  c++  java
  • C puzzles详解【9-12题】

    第九题

    #include <stdio.h>
    
    int main()
    {
            float f=0.0f;
            int i;
    
            for(i=0;i<10;i++)
                    f = f + 0.1f;
    
            if(f == 1.0f)
                    printf("f is 1.0 
    ");
            else
                    printf("f is NOT 1.0
    ");
    
            return 0;
    }

    知识点讲解:

    • 浮点寄存器

    浮点寄存器是FPU的组成部分。硬件架构不同,浮点寄存器的个数和位数也不同。X86架构的浮点寄存器有:

    1)8个80位的数据寄存器:FPR0~FPR7,数据寄存器的位数决定着计算机的计算精度,位数越多,计算精度越高;

    2)3个16位寄存器:1个标记寄存器,1个控制寄存器,1个状态寄存器;

    3)2个48位寄存器:1个指令指针,1个数据指针。

    FPU对浮点寄存器的操作有自己的一套指令,对于数据寄存器,不能直接使用这8个寄存器的名字,这8个数据寄存器被设计成首尾相连的堆栈,st(0)指向FPRx的栈顶。

    • 浮点数在寄存器中的二进制表示

    参考文章:

    http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html

    http://floating-point-gui.de/

    http://en.wikipedia.org/wiki/IEEE_754

    http://en.wikipedia.org/wiki/Floating_point

    根据国际标准IEEE 754,任意一个二进制浮点数V可以表示成下面的形式:

    V = (-1)S  × M × 2E

    关于S,M,E所占位数如下表所示:

    Type

    Total bits

    S

    E

    M

    Exponent bias

    Bits precision

    Single

    32

    1

    8

    23

    127

    24

    Double

    64

    1

    11

    52

    1023

    53

    Double extended

    80

    1

    15

    64

    16383

    64

    M:1≤M≤2,即M总是可以表示成1.xxx的形式,由于M第一位总是1,在寄存器内表示M时,第一位的1被舍去,只保存小数部分,所以节省了1位有效数字。以32位浮点数为例,M占23位,但有效数字的位数为24。

    E:存放E时,需要将E加上一个固定值,对于32位浮点数,固定值为127,对于64位浮点数,固定值为1023。更多关于E的讲解见第一个参考链接。

    举例:

    float x = 0.15625;

    0.15625用二进制表示为0.00101

    S=0, E=-3, M=1.01

    寄存器中存放E的部分应存放-3+127;

    寄存器中存放M的部分应存放01

    故0.15625在寄存器中的表示为:

    0 01111100 01000000000000000000000

    题目讲解:

    浮点数在计算机中并不能精确表示,表示值与实际值有微小的误差,这种误差会随着加减法叠加。比较浮点数不可简单地用“>”“<”“==”“!=”来判断。

    浮点数的比较方法可以参考

    http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

    第十题

    #include <stdio.h>
    
    int main()
    {
            int a = 1,2;
            printf("a : %d
    ",a);
            return 0;
    }

    知识点讲解:

    • 逗号表达式

    逗号表达式的形式如下:

    (表达式1,表达式2,表达式3,……,表达式n)

    (1)逗号表达式的计算过程为从左往右逐个计算;

    (2)逗号表达式作为一个整体,它的值为最后一个表达式的值;

    (3)逗号运算符的优先级在所有运算符中最低。

    题目讲解

    由于逗号运算符的优先级最低,所以

    int a = 1,2;

    等同于

    int (a = 1),2

    故编译有误。

    应改为:

    int a = (1,2);

    第十一题

    #include <stdio.h>
    int main()
    {
            int i=43;
            printf("%d
    ",printf("%d",printf("%d",i)));
            return 0;
    }

    题目讲解:

    printf的返回值为打印的字符数,man 3 printf中的描述如下:

    Upon successful return, these functions return the number of characters printed (not including the trailing “0”used to end output to strings).

    故这段程序的输出为:4321

    第十二题

      void duff(register char *to, register char *from, register int count)
      {
          register int n=(count+7)/8;
          switch(count%8){
          case 0: do{ *to++ = *from++;
          case 7:  *to++ = *from++;
          case 6: *to++ = *from++;
          case 5: *to++ = *from++;
          case 4: *to++ = *from++;
          case 3: *to++ = *from++;
          case 2: *to++ = *from++;
          case 1: *to++ = *from++;
                  }while( --n >0);
          }
      }

    知识点讲解

    • register关键字

    通知编译器将register变量“尽可能”地放入寄存器当中,以提高对该变量的存取速度。普通变量放在内存中,其存取速度跟内存速度相当,register变量存放于寄存器当中,其存取速度和cpu速度相当。对于需要频繁循环访问的变量定义成register类型可提高程序运行效率。

    • a++与++a的区别

    a++:取a的地址,把它的值装入寄存器,然后增加内存中a的值;

    ++a:取a的地址,增加内存中a的值,把a的值装入寄存器。

    • C语言运算符优先级

    关于C语言运算优先级参考:

    http://www.slyar.com/blog/c-operator-priority.html

    对于*to++,*和++属于同级运算符,结合方向为从右到左,故*to++ == *(to++),操作步骤有三:

    1、取to的值到寄存器;

    2、将to的值加1;

    3、取步骤一的寄存器中的地址指向的值。

    “*to++ = *from++;”的含义为从源地址处复制一个字节到目的地址处,并将原地址和目的地址分别向后移一个字节。

    • Duff’s Device

    关于达夫设备参考:

    http://www.drdobbs.com/a-reusable-duff-device/184406208

    达夫设备是串行复制的一种优化实现,通过减少循环次数来提高程序的执行效率。

    当要把一段数据从源地址复制到目的地址时,你会用什么样的方法?

    境界一:

    void my_copy_1(register char *to, register char *from, register int count)
    {
        while(count-- > 0)
        {
            *to++ = *from++;
        }
    }

    点评:这段代码简洁易懂,但执行效率却不高。每复制完一个字节后都要执行三个附加动作:1.跳转到循环开始处;2.改变count的值;3.将count值和0比较。这三个附加动作消耗的时间远远大于复制动作消耗的时间,代码有点头重脚轻了。

    想一想如果你是cpu,每复制一个字节都让你检测一次是否超过count值,你一定觉得你的主人好烦。如果能让cpu顺溜地,没有阻碍地一直复制下去就好了……

    境界二:

    void my_copy_2(register char *to, register char *from, register int count)
    {
        register int n_block = count/8;
        register int n_left = count%8;        
    
        while (n_block-- > 0)    
        {
            *to++ = *from++;
            *to++ = *from++;
            *to++ = *from++;
            *to++ = *from++;
            *to++ = *from++;
            *to++ = *from++;
            *to++ = *from++;
            *to++ = *from++;
        }
    
        while (n_left-- > 0)
        {
            *to++ = *from;
        }
    }

    点评:复制大段数据时,让cpu呆头呆脑地一直copy下去那是最快的,但是我们总得检查已经复制的数据量是否超过了设定值。一步一回头显然没有必要,N歩一回头还是可以考虑的。上段程序中把N设为了8,当然也可以根据实际需要设成其他的值。

    上面程序中对“零碎数据”的处理仍是采用“一步一回头”的老方法,对于这段数据的处理,有没有更简洁的方法呢?

    境界三:

    void my_copy_3(register char *to, register char *from, register int count)
    {
        register int n_block = (count+7)/8;
        int n_left = count%8;
    
        switch (n_left)
        {
            case 0: do{*to++ = *from++;
            case 7: *to++ = *from++;
            case 6: *to++ = *from++;
            case 5: *to++ = *from++;
            case 4: *to++ = *from++;
            case 3: *to++ = *from++;
            case 2: *to++ = *from++;
            case 1: *to++ = *from++;
                } while(--n_block > 0);
        }
    }

    点评:上述代码先处理零碎的数据,再处理块状的数据。利用switch语句,根据零碎数据量跳转到相应的case标号处,如果零碎数据量为7,那就一路往下执行7次赋值动作,为6就一路执行6次赋值动作,以此类推……这种方法执行比较操作的次数最少。上面这段串行复制的方法就叫做“达夫设备”。

    当然我们也可以用宏来实现达夫设备,宏定义如下:

    #define DUFF_DEVICE_8(aCount, aAction) 
    do { 
    int count_ = (aCount); 
    int times_ = (count_ + 7) >> 3; 
    switch (count_ & 7) 
    { 
    case 0: do{aAction; 
    case 7: aAction; 
    case 6: aAction; 
    case 5: aAction; 
    case 4: aAction; 
    case 3: aAction; 
    case 2: aAction; 
    case 1: aAction; 
        } while(--times_ > 0); 
    } 
    }while (0)

    点评:num除以8和num对8取余有两种方法:一种是num/8,num%8;一种是num>>3,num&7。后一种方法高效一点。

  • 相关阅读:
    POJ 1044: Date bugs
    POJ 1017: Packets
    POJ 1014: Dividing
    POJ 1012: Joseph
    POJ 1011: Sticks
    POJ 1008: Maya Calendar
    POJ 1005: I Think I Need a Houseboat
    为什么要自动化测试
    微软CodeDom模型学习笔记(全)
    概念完整性
  • 原文地址:https://www.cnblogs.com/tanghuimin0713/p/3987525.html
Copyright © 2011-2022 走看看