在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 練習(xí)23:認(rèn)識(shí)達(dá)夫設(shè)備
練習(xí)9:數(shù)組和字符串
練習(xí)6:變量類(lèi)型
練習(xí)3:格式化輸出
練習(xí)4:Valgrind 介紹
練習(xí)28:Makefile 進(jìn)階
練習(xí)14:編寫(xiě)并使用函數(shù)
練習(xí)21:高級(jí)數(shù)據(jù)類(lèi)型和控制結(jié)構(gòu)
練習(xí)20:Zed的強(qiáng)大的調(diào)試宏
練習(xí)18:函數(shù)指針
練習(xí)0:準(zhǔn)備
練習(xí)15:指針,可怕的指針
練習(xí)27:創(chuàng)造性和防御性編程
練習(xí)22:棧、作用域和全局
練習(xí)10:字符串?dāng)?shù)組和循環(huán)
練習(xí)8:大小和數(shù)組
練習(xí)16:結(jié)構(gòu)體和指向它們的指針
練習(xí)7:更多變量和一些算術(shù)
練習(xí)23:認(rèn)識(shí)達(dá)夫設(shè)備
練習(xí)12:If,Else If,Else
練習(xí)2:用Make來(lái)代替Python
練習(xí)1:?jiǎn)⒂镁幾g器
練習(xí)11:While循環(huán)和布爾表達(dá)式
練習(xí)5:一個(gè)C程序的結(jié)構(gòu)
練習(xí)24:輸入輸出和文件
練習(xí)25:變參函數(shù)
練習(xí)13:Switch語(yǔ)句
練習(xí)19:一個(gè)簡(jiǎn)單的對(duì)象系統(tǒng)
練習(xí)26:編寫(xiě)第一個(gè)真正的程序
導(dǎo)言:C的笛卡爾之夢(mèng)
練習(xí)17:堆和棧的內(nèi)存分配

練習(xí)23:認(rèn)識(shí)達(dá)夫設(shè)備

這個(gè)練習(xí)是一個(gè)腦筋急轉(zhuǎn)彎,我會(huì)向你介紹最著名的C語(yǔ)言黑魔法之一,叫做“達(dá)夫設(shè)備”,以“發(fā)明者”湯姆·達(dá)夫的名字命名。這一強(qiáng)大(或邪惡?)的代碼中,幾乎你學(xué)過(guò)的任何東西都被包裝在一個(gè)小的結(jié)構(gòu)中。弄清它的工作機(jī)制也是一個(gè)好玩的謎題。

C的一部分樂(lè)趣來(lái)源于這種神奇的黑魔法,但這也是使C難以使用的地方。你最好能夠了解這些技巧,因?yàn)樗麜?huì)帶給你關(guān)于C語(yǔ)言和你計(jì)算機(jī)的深入理解。但是,你應(yīng)該永遠(yuǎn)都不要使用它們,并總是追求簡(jiǎn)單易讀的代碼。

達(dá)夫設(shè)備由湯姆·達(dá)夫“發(fā)現(xiàn)”(或創(chuàng)造),它是一個(gè)C編譯器的小技巧,本來(lái)不應(yīng)該能夠正常工作。我并不想告訴你做了什么,因?yàn)檫@是一個(gè)謎題,等著你來(lái)思考并嘗試解決。你需要運(yùn)行這段代碼,之后嘗試弄清它做了什么,以及為什么可以這樣做。

#include <stdio.h>
#include <string.h>
#include "dbg.h"

int normal_copy(char *from, char *to, int count)
{
    int i = 0;

    for(i = 0; i < count; i++) {
        to[i] = from[i];
    }

    return i;
}

int duffs_device(char *from, char *to, int count)
{
    {
        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);
        }
    }

    return count;
}

int zeds_device(char *from, char *to, int count)
{
    {
        int n = (count + 7) / 8;

        switch(count % 8) {
            case 0:
            again: *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++;
                    if(--n > 0) goto again;
        }
    }

    return count;
}

int valid_copy(char *data, int count, char expects)
{
    int i = 0;
    for(i = 0; i < count; i++) {
        if(data[i] != expects) {
            log_err("[%d] %c != %c", i, data[i], expects);
            return 0;
        }
    }

    return 1;
}

int main(int argc, char *argv[])
{
    char from[1000] = {'a'};
    char to[1000] = {'c'};
    int rc = 0;

    // setup the from to have some stuff
    memset(from, 'x', 1000);
    // set it to a failure mode
    memset(to, 'y', 1000);
    check(valid_copy(to, 1000, 'y'), "Not initialized right.");

    // use normal copy to 
    rc = normal_copy(from, to, 1000);
    check(rc == 1000, "Normal copy failed: %d", rc);
    check(valid_copy(to, 1000, 'x'), "Normal copy failed.");

    // reset
    memset(to, 'y', 1000);

    // duffs version
    rc = duffs_device(from, to, 1000);
    check(rc == 1000, "Duff's device failed: %d", rc);
    check(valid_copy(to, 1000, 'x'), "Duff's device failed copy.");

    // reset
    memset(to, 'y', 1000);

    // my version
    rc = zeds_device(from, to, 1000);
    check(rc == 1000, "Zed's device failed: %d", rc);
    check(valid_copy(to, 1000, 'x'), "Zed's device failed copy.");

    return 0;
error:
    return 1;
}

這段代碼中我編寫(xiě)了三個(gè)版本的復(fù)制函數(shù):

normal_copy

使用普通的for循環(huán)來(lái)將字符從一個(gè)數(shù)組復(fù)制到另一個(gè)。

duffs_device

這個(gè)就是稱(chēng)為“達(dá)夫設(shè)備”的腦筋急轉(zhuǎn)彎,以湯姆·達(dá)夫的名字命名。這段有趣的邪惡代碼應(yīng)歸咎于他。

zeds_device

“達(dá)夫設(shè)備”的另一個(gè)版本,其中使用了goto來(lái)讓你發(fā)現(xiàn)一些線索,關(guān)于duffs_device中奇怪的do-while做了什么。

在往下學(xué)習(xí)之前仔細(xì)了解這三個(gè)函數(shù),并試著自己解釋代碼都做了什么。

你會(huì)看到什么

這個(gè)程序沒(méi)有任何輸出,它只會(huì)執(zhí)行并退出。你應(yīng)當(dāng)在Valgrind下運(yùn)行它并確保沒(méi)有任何錯(cuò)誤。

解決謎題

首先需要了解的一件事,就是C對(duì)于它的一些語(yǔ)法是弱檢查的。這就是你可以將do-while的一部分放入switch語(yǔ)句的一部分的原因,并且在其它地方的另一部分還可以正常工作。如果你觀察帶有goto again的我的版本,它實(shí)際上更清晰地解釋了工作原理,但要確保你理解了這一部分是如何工作的。

第二件事是switch語(yǔ)句的默認(rèn)貫穿機(jī)制可以讓你跳到指定的case,并且繼續(xù)運(yùn)行知道switch結(jié)束。

最后的線索是count % 8以及頂端對(duì)n的計(jì)算。

現(xiàn)在,要理解這些函數(shù)的工作原理,需要完成下列事情:

  • 將代碼抄寫(xiě)在一張紙上。
  • 當(dāng)每個(gè)變量在switch之前初始化時(shí),在紙的空白區(qū)域,把每個(gè)變量列在表中。
  • 按照switch的邏輯模擬執(zhí)行代碼,之后再正確的case處跳出。
  • 更新變量表,包括to、from和它們所指向的數(shù)組。
  • 當(dāng)你到達(dá)while或者我的goto時(shí),檢查你的變量,之后按照邏輯返回do-while頂端,或者again標(biāo)簽所在的地方。
  • 繼續(xù)這一手動(dòng)的執(zhí)行過(guò)程,更新變量,直到確定明白了代碼如何運(yùn)作。

為什么寫(xiě)成這樣?

當(dāng)你弄明白它的實(shí)際工作原理時(shí),最終的問(wèn)題是:?jiǎn)柺裁匆汛a寫(xiě)成這樣?這個(gè)小技巧的目的是手動(dòng)編寫(xiě)“循環(huán)展開(kāi)”。大而長(zhǎng)的循環(huán)會(huì)非常慢,所以提升速度的一個(gè)方法就是找到循環(huán)中某個(gè)固定的部分,之后在循環(huán)中復(fù)制代碼,序列化地展開(kāi)。例如,如果你知道一個(gè)循環(huán)會(huì)執(zhí)行至少20次,你就可以將這20次的內(nèi)容直接寫(xiě)在源代碼中。

達(dá)夫設(shè)備通過(guò)將循環(huán)展開(kāi)為8個(gè)迭代塊,來(lái)完成這件事情。這是個(gè)聰明的辦法,并且可以正常工作。但是目前一個(gè)好的編譯器也會(huì)為你完成這些。你不應(yīng)該這樣做,除非少數(shù)情況下你證明了它的確可以提升速度。

附加題

  • 不要再這樣寫(xiě)代碼了。
  • 查詢(xún)維基百科的“達(dá)夫設(shè)備”詞條,并且看看你能不能找到錯(cuò)誤。將它與這里的版本對(duì)比,并且閱讀文章來(lái)試著理解,為什么維基百科上的代碼在你這里不能正常工作,但是對(duì)于湯姆·達(dá)夫可以。
  • 創(chuàng)建一些宏,來(lái)自動(dòng)完成任意長(zhǎng)度的這種設(shè)備。例如,你想創(chuàng)建32個(gè)case語(yǔ)句,并且不想手動(dòng)把它們都寫(xiě)出來(lái)時(shí),你會(huì)怎么辦?你可以編寫(xiě)一次展開(kāi)8個(gè)的宏嗎?
  • 修改main函數(shù),執(zhí)行一些速度檢測(cè),來(lái)看看哪個(gè)實(shí)際上更快。
  • 查詢(xún)memcpy、memmovememset,并且也比較一下它們的速度。
  • 不要再這樣寫(xiě)代碼了!