第九题

  1. #include <stdio.h>
  2.  
  3. int main()
  4. {
  5. float f=0.0f;
  6. int i;
  7.  
  8. for(i=;i<;i++)
  9. f = f + 0.1f;
  10.  
  11. if(f == 1.0f)
  12. printf("f is 1.0 \n");
  13. else
  14. printf("f is NOT 1.0\n");
  15.  
  16. return ;
  17. }

知识点讲解:

  • 浮点寄存器

浮点寄存器是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的讲解见第一个参考链接。

举例:

  1. 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

第十题

  1. #include <stdio.h>
  2.  
  3. int main()
  4. {
  5. int a = ,;
  6. printf("a : %d\n",a);
  7. return ;
  8. }

知识点讲解:

  • 逗号表达式

逗号表达式的形式如下:

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

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

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

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

题目讲解

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

  1. int a = ,;

等同于

  1. int (a = ),

故编译有误。

应改为:

  1. int a = (,);

第十一题

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i=;
  5. printf("%d\n",printf("%d",printf("%d",i)));
  6. return ;
  7. }

题目讲解:

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

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

故这段程序的输出为:4321

第十二题

  1. void duff(register char *to, register char *from, register int count)
  2. {
  3. register int n=(count+)/;
  4. switch(count%){
  5. case : do{ *to++ = *from++;
  6. case : *to++ = *from++;
  7. case : *to++ = *from++;
  8. case : *to++ = *from++;
  9. case : *to++ = *from++;
  10. case : *to++ = *from++;
  11. case : *to++ = *from++;
  12. case : *to++ = *from++;
  13. }while( --n >);
  14. }
  15. }

知识点讲解

  • 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

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

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

境界一:

  1. void my_copy_1(register char *to, register char *from, register int count)
  2. {
  3. while(count-- > )
  4. {
  5. *to++ = *from++;
  6. }
  7. }

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

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

境界二:

  1. void my_copy_2(register char *to, register char *from, register int count)
  2. {
  3. register int n_block = count/;
  4. register int n_left = count%;
  5.  
  6. while (n_block-- > )
  7. {
  8. *to++ = *from++;
  9. *to++ = *from++;
  10. *to++ = *from++;
  11. *to++ = *from++;
  12. *to++ = *from++;
  13. *to++ = *from++;
  14. *to++ = *from++;
  15. *to++ = *from++;
  16. }
  17.  
  18. while (n_left-- > )
  19. {
  20. *to++ = *from;
  21. }
  22. }

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

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

境界三:

  1. void my_copy_3(register char *to, register char *from, register int count)
  2. {
  3. register int n_block = (count+)/;
  4. int n_left = count%;
  5.  
  6. switch (n_left)
  7. {
  8. case : do{*to++ = *from++;
  9. case : *to++ = *from++;
  10. case : *to++ = *from++;
  11. case : *to++ = *from++;
  12. case : *to++ = *from++;
  13. case : *to++ = *from++;
  14. case : *to++ = *from++;
  15. case : *to++ = *from++;
  16. } while(--n_block > );
  17. }
  18. }

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

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

  1. #define DUFF_DEVICE_8(aCount, aAction) \
  2. do { \
  3. int count_ = (aCount); \
  4. int times_ = (count_ + ) >> ; \
  5. switch (count_ & ) \
  6. { \
  7. case : do{aAction; \
  8. case : aAction; \
  9. case : aAction; \
  10. case : aAction; \
  11. case : aAction; \
  12. case : aAction; \
  13. case : aAction; \
  14. case : aAction; \
  15. } while(--times_ > ); \
  16. } \
  17. }while ()

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

C puzzles详解【9-12题】的更多相关文章

  1. C puzzles详解【13-15题】

    第十三题 int CountBits(unsigned int x) { ; while(x) { count++; x = x&(x-); } return count; } 知识点讲解 位 ...

  2. C puzzles详解【51-57题】

    第五十一题 Write a C function which does the addition of two integers without using the '+' operator. You ...

  3. C puzzles详解【46-50题】

    第四十六题 What does the following macro do? #define ROUNDUP(x,n) ((x+n-1)&(~(n-1))) 题目讲解: 参考:http:// ...

  4. C puzzles详解【38-45题】

    第三十八题 What is the bug in the following program? #include <stdlib.h> #include <stdio.h> # ...

  5. C puzzles详解【34-37题】

    第三十四题 The following times. But you can notice that, it doesn't work. #include <stdio.h> int ma ...

  6. C puzzles详解【31-33题】

    第三十一题 The following is a simple C program to read and print an integer. But it is not working proper ...

  7. C puzzles详解【26-30题】

    第二十六题(不会) The following is a simple program which implements a minimal version of banner command ava ...

  8. C puzzles详解【21-25题】

    第二十一题 What is the potential problem with the following C program? #include <stdio.h> int main( ...

  9. C puzzles详解【16-20题】

    第十六题 The following is a small C program split across files. What do you expect the output to be, whe ...

随机推荐

  1. Intellisense in Visual Studio for Microsoft Dynamics CRM 2016

    Intellisense in Visual Studio for Microsoft Dynamics CRM 2016 posted by dynamicsnick on may 18, 2016 ...

  2. php mssql 中文各种乱码

    1 查询输出时乱码  (SELECT ) 因为MSSQL 数据库一般都是 GBK 编码,所以在php页面中加入 header('Content-Type:text/html; charset=GBK' ...

  3. String Format for DateTime

    This example shows how to format DateTime using String.Format method. All formatting can be done als ...

  4. HDU 5834 [树形dp]

    /* 题意:n个点组成的树,点和边都有权值,当第一次访问某个点的时候获得利益为点的权值 每次经过一条边,丢失利益为边的权值.问从第i个点出发,获得的利益最大是多少. 输入: 测试样例组数T n n个数 ...

  5. 20. Valid Parentheses(stack)

    Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the inpu ...

  6. 临时改GCC编译器,重启后失效

    临时改GCC编译器,重启后失效.例如,用如下命令: export CROSS_COMPILE= <gcc 文件所在的目录>/arm-linux-gnueabihf- 本例中使用的命令如下: ...

  7. 查看用户的SQL执行历史

    程序开发少不来SQL,基本都是基于SQL开发,程序仅仅起一个流程控制的作用.但是数据库本身存在许多内置的视图或者内置的表,如果打算研究SQL执行的效率已经SQL执行的历史记录,通过这些视图可以知道. ...

  8. C和C++头文件的不同

    #include <IOSTREAM.h>void main(){    std::cout<<"Hello,World!"<<std::end ...

  9. C#(Visual Studio) AssemblyInfo

    AssemblyInfo .NET Project的Properties文件夹下会自动生成一个AssemblyInfo.cs的文件,该文件包含的信息和项目->右键->属性->Appl ...

  10. 【LeetCode】3.Longest Substring Without Repeating Characters 最长无重复子串

    题目: Given a string, find the length of the longest substring without repeating characters. Examples: ...