C puzzles詳解【9-12題】

來源:互聯網
上載者:User

標籤:style   blog   http   color   io   使用   ar   strong   for   

第九題

#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 \n");        else                printf("f is NOT 1.0\n");        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\n",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\n",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。後一種方法高效一點。

C puzzles詳解【9-12題】

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.