C puzzles详解【9-12题】
第九题
#include <stdio.h> int main()
{
float f=0.0f;
int i; for(i=;i<;i++)
f = f + 0.1f; if(f == 1.0f)
printf("f is 1.0 \n");
else
printf("f is NOT 1.0\n"); return ;
}
知识点讲解:
- 浮点寄存器
浮点寄存器是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://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 = ,;
printf("a : %d\n",a);
return ;
}
知识点讲解:
- 逗号表达式
逗号表达式的形式如下:
(表达式1,表达式2,表达式3,……,表达式n)
(1)逗号表达式的计算过程为从左往右逐个计算;
(2)逗号表达式作为一个整体,它的值为最后一个表达式的值;
(3)逗号运算符的优先级在所有运算符中最低。
题目讲解
由于逗号运算符的优先级最低,所以
int a = ,;
等同于
int (a = ),
故编译有误。
应改为:
int a = (,);
第十一题
#include <stdio.h>
int main()
{
int i=;
printf("%d\n",printf("%d",printf("%d",i)));
return ;
}
题目讲解:
printf的返回值为打印的字符数,man 3 printf中的描述如下:
Upon successful return, these functions return the number of characters printed (not including the trailing “\”used to end output to strings).
故这段程序的输出为:4321
第十二题
void duff(register char *to, register char *from, register int count)
{
register int n=(count+)/;
switch(count%){
case : do{ *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
}while( --n >);
}
}
知识点讲解
- 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-- > )
{
*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/;
register int n_left = count%; while (n_block-- > )
{
*to++ = *from++;
*to++ = *from++;
*to++ = *from++;
*to++ = *from++;
*to++ = *from++;
*to++ = *from++;
*to++ = *from++;
*to++ = *from++;
} while (n_left-- > )
{
*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+)/;
int n_left = count%; switch (n_left)
{
case : do{*to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
case : *to++ = *from++;
} while(--n_block > );
}
}
点评:上述代码先处理零碎的数据,再处理块状的数据。利用switch语句,根据零碎数据量跳转到相应的case标号处,如果零碎数据量为7,那就一路往下执行7次赋值动作,为6就一路执行6次赋值动作,以此类推……这种方法执行比较操作的次数最少。上面这段串行复制的方法就叫做“达夫设备”。
当然我们也可以用宏来实现达夫设备,宏定义如下:
#define DUFF_DEVICE_8(aCount, aAction) \
do { \
int count_ = (aCount); \
int times_ = (count_ + ) >> ; \
switch (count_ & ) \
{ \
case : do{aAction; \
case : aAction; \
case : aAction; \
case : aAction; \
case : aAction; \
case : aAction; \
case : aAction; \
case : aAction; \
} while(--times_ > ); \
} \
}while ()
点评:num除以8和num对8取余有两种方法:一种是num/8,num%8;一种是num>>3,num&7。后一种方法高效一点。
C puzzles详解【9-12题】的更多相关文章
- C puzzles详解【13-15题】
第十三题 int CountBits(unsigned int x) { ; while(x) { count++; x = x&(x-); } return count; } 知识点讲解 位 ...
- C puzzles详解【51-57题】
第五十一题 Write a C function which does the addition of two integers without using the '+' operator. You ...
- C puzzles详解【46-50题】
第四十六题 What does the following macro do? #define ROUNDUP(x,n) ((x+n-1)&(~(n-1))) 题目讲解: 参考:http:// ...
- C puzzles详解【38-45题】
第三十八题 What is the bug in the following program? #include <stdlib.h> #include <stdio.h> # ...
- C puzzles详解【34-37题】
第三十四题 The following times. But you can notice that, it doesn't work. #include <stdio.h> int ma ...
- C puzzles详解【31-33题】
第三十一题 The following is a simple C program to read and print an integer. But it is not working proper ...
- C puzzles详解【26-30题】
第二十六题(不会) The following is a simple program which implements a minimal version of banner command ava ...
- C puzzles详解【21-25题】
第二十一题 What is the potential problem with the following C program? #include <stdio.h> int main( ...
- C puzzles详解【16-20题】
第十六题 The following is a small C program split across files. What do you expect the output to be, whe ...
随机推荐
- Gerrit清单库配置(转载)
From:http://fatalove.iteye.com/blog/1340334 gerrit清单库是用来配合repo使用的.清单库中列出了gerrit服务器上的其他版本库. 客户端通过repo ...
- linux下修改path变量(转载)
比如要把/etc/apache/bin目录添加到PATH中 1.#PATH=$PATH:/etc/apache/bin 使用这种方法,每当登出PATH就会恢复 2.#vi /etc/profile 在 ...
- 清除xcode里面的mobileprovision文件
清除所有的mobileprovision cd ~/Library/MobileDevice/Provisioning\ Profiles/ 然后删除里面所有的mobileprovision文件 rm ...
- C开源hash项目uthash
uthash 是C的比较优秀的开源代码,它实现了常见的hash操作函数,例如查找.插入.删除等.该套开源代码采用宏的方式实现hash函数的相关功能,支持C语言的任意数据结构最为key值,甚至可以采用多 ...
- rand()和srand()区别
标准库<cstdlib>提供两个帮助生成伪随机数的函数: 函数一:int rand(void):从srand (seed)中指定的seed开始,返回一个[seed, RAND_MAX(0x ...
- 关于asp.net 网站网站发布时提示:错误 27 对路径 AppData\Local\Temp\~632b\bin\App_Code.compil的解决方法
关于asp.net 网站网站发布时提示:错误 27 对路径 AppData\Local\Temp\~632b\bin\App_Code.compil的解决方法 问题如下图所示,方法是去掉: <i ...
- IOS插件管理器: alcatraz
官网 http://alcatraz.io/ 推荐几个应用,均可以由alcatraz来进行安装: 1. 格式化 clangformat. 插入自定义的格式化文件:https://raw.githubu ...
- SVN-服务器搭建、apache2整合、eclipse使用
如题,分成3个部分: 1.SVN服务器搭建.操作系统Ubuntu 14.04.2 LTS.具体方法度娘很多,不再细数. 安装:sudo apt-get install subversion 创建版本库 ...
- 走出测试,走向CEO
飞测说:大家好,我们又见面了,我是黑夜小怪.不巧,今晚加班回来路上,湿身了,淋了个落汤鸡,不过明天也许可以看海了,也就呵呵了,原本想回来后聊些技术的,现在突然想先聊聊我的一些想法,仅供交流. 走出测试 ...
- EndNote文献管理
一直想写个博客,但是一直没有好好坐下来对自己工作进行一个梳理.从今天开始吧,争取多写一点. 今天,先介绍一下科技论文写作中经常使用的一款软件EndNote,这个软件,掌握它的使用方法后会觉得很方便:但 ...