• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 74, 文章 - 0, 評論 - 26, 引用 - 0
            數(shù)據(jù)加載中……

            新時代的門前 32位世界中的64位編程

            為16、32、64位架構(gòu)編寫可移植代碼

              與16位相比,32位意味著程序更快、可直接尋址訪問更多的內(nèi)存和更好的處理器架構(gòu)。鑒于此,越來越多的程序員已經(jīng)開始考慮利用64位處理器所帶來的巨大優(yōu)勢了。

              克雷研究(Cray Research 應(yīng)為品牌名)計算機已經(jīng)開始使用64位字,并可訪問更大的內(nèi)存地址。然而,作為正在向開發(fā)標準軟件和與其他操作系統(tǒng)相互努力的一部分,我們已經(jīng)停止了移植那些原本基于32位處理器的代碼。事實上,我們不斷遇到我們稱之為“32位主義”的代碼,這些代碼都是在假定機器字長為32位的情況下編寫的,因而很難移植這種代碼,所以必須確立一些簡單的指導方針,以便助于編寫跨16、32、64位處理器平臺的代碼。

              由于有一些遺留問題,C語言在數(shù)據(jù)類型和數(shù)據(jù)構(gòu)造方面,顯得有點過剩了。可以使用的不僅僅是char、short、int和long類型,還有相應(yīng)的unsigned(無符號)類型,當然你可在結(jié)構(gòu)(structure)和聯(lián)合(union)中混合使用,可以在聯(lián)合中包含結(jié)構(gòu),再在結(jié)構(gòu)中包含聯(lián)合,如果還嫌數(shù)據(jù)類型不夠復雜,還可以轉(zhuǎn)換成比特位,當然也可以把一種數(shù)據(jù)類型轉(zhuǎn)換成另一種你想要的數(shù)據(jù)類型。正是因為這些工具太強大,如果不安全地使用它們,就有可能會傷到自己了。

              高級代碼的高級結(jié)構(gòu)

              在Kernighan和Plauger經(jīng)典的《The Elements of Programming Style》一書中,他們的建議是“選擇使程序看上去簡單的數(shù)據(jù)表示法”。對個人而言,這意味著為高級編程使用高級數(shù)據(jù)結(jié)構(gòu),而低級編程使用低級數(shù)據(jù)結(jié)構(gòu)。

              在我們移植的程序中,有一個常見的32位主義bug,不如拿它來做個例子,科內(nèi)爾大學編寫的用于網(wǎng)間互訪的路由協(xié)議引擎,在伯克利網(wǎng)絡(luò)環(huán)境下(指TCP/IP),自然是使用inet_addr( )來把表示Internet地址的字符串轉(zhuǎn)換成二進制形式。Internet地址碰巧也是32位的,就如運行伯克利網(wǎng)絡(luò)系統(tǒng)的其他計算機一樣,都是有著同樣的字寬。

              但也存在著有關(guān)Internet地址的高級定義:in_ addr結(jié)構(gòu)。這個結(jié)構(gòu)定義包括了子域s_ addr,它是一個包含了Internet地址的unsigned long標量。inet_addr()接受一個指向字符的指針,并且返回一個unsigned long,在轉(zhuǎn)換地址字符串過程中如果發(fā)生錯誤,inet_addr將返回-1。

              程序Gated讀取以文本格式存放Internet地址的配置文件,并把它們放入sockaddr_in(這是一個包含了結(jié)構(gòu)in_addr的高級結(jié)構(gòu))。例1(a)中的代碼可以在32位電腦上正常運行,但移植到克雷研究計算機上,卻無法運行,為什么呢?

              例1:高級代碼的高級結(jié)構(gòu)

              (a)

            struct sockaddr_in saddrin
            char *str;

            if ((saddrin.sin_addr.s_addr = inet_addr(str)) == (unsigned long)-1) {
             do_some_error_handling;
            }

              (b)

            struct sockaddr_in saddrin
            char *str;

            if (inet_aton(str, &saddrin.sin_addr) ! = OK) {
             do_some_error_handling;
            }

              因為只要inet_addr能夠正確地解析字符串,那么一切OK。當inet_addr在64位計算機上返回一個錯誤時,這段代碼卻未能捕捉到。你必須要考慮比較語句中的數(shù)據(jù)位寬,來確定到底是哪出了錯。

              首先,inet_addr返回錯誤值——unsigned long -1,在64位中表示為比特位全為1,這個值被存儲在結(jié)構(gòu)in_addr下的子域s_addr中,而in_addr必須是32位來匹配Internet地址,所以它是一個32比特位的unsigned int(在我們的編譯器上,int是64位)。現(xiàn)在我們存儲進32個1,存儲進的值將與unsigned long -1比較。當我們存儲32個1于unsigned int時,編譯器自動把32位提升為64位;這樣,數(shù)值0x00000000 ffffffff與0xffffffff ffffffff的比較當然就失敗了。這是一個很難發(fā)現(xiàn)的bug,特別是在這種因為32位到64位的隱式提升上。

              那我們對這個bug怎么辦呢?一個解決方法是在語句中比較0xffffffff,而不是-1,但這又會使代碼更加依賴于特定大小的對象。另一個方法是,使用一個中間的unsigned long變量,從而在把結(jié)果存入sockaddr_in前,執(zhí)行比較,但這會讓程序代碼更復雜。

              真正的問題所在是,我們期望一個unsigned long值與一個32位量(如Internet地址)相等。Internet地址必須以32位形式進行存儲,但有些時候用一個標量,來訪問這個地址的一部分,是非常方便的。在32位字長的電腦中,用一個long數(shù)值(常被當作32位)來訪問這個地址,看上去沒什么問題。讓我們暫時不想一個低級的數(shù)據(jù)項(32位Internet地址)是否與一個機器字相等,那么高級數(shù)據(jù)類型結(jié)構(gòu)in_addr就應(yīng)該被一直使用。因為in_addr中沒有無效值,那么應(yīng)有一個單獨的狀態(tài)用作返回值。

              解決方案是定義一個新的函數(shù),就像inet_addr那樣,但返回一個狀態(tài)值,而且接受一個結(jié)構(gòu)in_addr作為參數(shù),參見例1(b)。因為高級的數(shù)據(jù)元素可以一直使用,而返回的值是沒有溢出的,所以這個代碼是可以跨架構(gòu)移植的,而不管字長是多少。雖然伯克利發(fā)布了NET2,其中的確定義了一個新的函數(shù)inet_aton(),但如果試著改變inet_addr()中的代碼,將會損壞許多程序。

              低級代碼的低級結(jié)構(gòu)

              低級編程意味著直接操縱物理設(shè)備或者特定協(xié)議的通訊格式,例如,設(shè)備驅(qū)動程序經(jīng)常使用非常精確的位模式來操縱控制寄存器。此外,網(wǎng)絡(luò)協(xié)議通過特定的比特位模式傳輸數(shù)據(jù)項時,也必須適當?shù)剞D(zhuǎn)譯。

              為了操縱物理數(shù)據(jù)項,此處的數(shù)據(jù)結(jié)構(gòu)必須準確地反映被操縱的內(nèi)容。比特位是一個不錯的選擇,因為它們正好指定了比特的位數(shù)及排列。事實上,正是這種精確度,使比特位相對于short、int、long,更好地映像了物理結(jié)構(gòu)(short、int、long會因為電腦的不同而改變,而比特位不會)。

              當映像一個物理結(jié)構(gòu)時,是通過定義格式來使比特位達到這種精度的,這就使你必須一直使用一種編碼風格來訪問結(jié)構(gòu)。此時的每個位域都是命名的,你寫出的代碼可直接訪問這些位域。當訪問物理結(jié)構(gòu)時,有件事可能你并不想去做,那就是使用標量數(shù)組(short、int、or long),訪問這些數(shù)組的代碼都假定存在一個特定的比特位寬,當移植這些代碼到一臺使用不同字寬的電腦上時,就可能不正確了。

              在我們移植PEX圖像庫時遇到的一個問題,就涉及到其映像的協(xié)議消息結(jié)構(gòu)。在某臺電腦上,如果int整型的長度與消息中的元素一樣,那例2(a)中的代碼就會工作得很正常。32位的數(shù)據(jù)元素在32字長的電腦上沒有問題,拿到64位的克雷計算機上,它就出錯了。對例2(b)而言,不單要改變結(jié)構(gòu)定義,還要改變所有涉及到coord數(shù)組的代碼。這樣,擺在我們面前就有兩個選擇,要么重寫涉及此消息的所有代碼,要么定義一個低級的結(jié)構(gòu)和一個高級的結(jié)構(gòu),然后用一段特殊的代碼把數(shù)據(jù)從一個拷貝到另一個當中,不過我也不期望可以找出每個對zcoord = draw_ msg.coord[2]的引用,而且,當現(xiàn)在需要移植到一個新架構(gòu)上時,把所有的代碼都改寫成如例2(c)所示無疑是一項艱苦的工作。這個特殊的問題是由于忽視字長的不同而帶來的,所以不能假設(shè)在可移植的代碼中機器字長、short、int、long都是具有同樣的大小。

              例2:低級代碼的低級結(jié)構(gòu)

              (a)

            struct draw_msg {
             int objectid;
             int coord[3];
            }

              (b)

            struct draw_msg {
             int objectid:32;
             int coord1:32;
             int coord2:32;
             int coord3:32;
            }

              (c)

            int *iptr, *optr, *limit;
            int xyz[3];

            iptr = draw_msg.coord;
            limit = draw_msg.coord + sizeof(draw_msg.coord);

            optr = xyz;
            while (iptr < limit)
            *optr++ = *iptr++;
            結(jié)構(gòu)打包和字對齊

               正是因為編譯器會對結(jié)構(gòu)進行打包,所以不同計算機上字長的變化,還導致了另一個問題。C編譯器在字(word)的邊界上對齊字長,當具有一個字長的數(shù)據(jù)后面緊接著一個較小的數(shù)據(jù)時,這種方法會產(chǎn)生內(nèi)存空缺(不過也有例外,比如說當有足夠多的小數(shù)據(jù)剛好可以填充一個字時)。

               一些聰明的程序員在聲明聯(lián)合時,往往在其中會帶有兩個或更多的結(jié)構(gòu),其中一個結(jié)構(gòu)剛好填充聯(lián)合,另一個則可以用來從不同的角度來看待這個聯(lián)合,參見例3(a)。假設(shè)這段代碼是為16位字長的計算機所寫,int為16位,long為32位,那么存取這個結(jié)構(gòu)的代碼將會得到正常的映射關(guān)系(如圖1),而例3(b)中的代碼也會按預期的那樣工作。可是,如果這段代碼一旦移植到另一臺具有32位字長的計算機上時,映射關(guān)系就改變了。如果新計算機上的編譯器允許你使用16位的int,那么字的對齊就會像圖2所示了,或者如果編譯器遵循K&R約定,那么int將會和一個字(32比特)一樣長,對齊就如圖3所示,在任一情況下,這都將導致問題。




              例3:結(jié)構(gòu)打包和字對齊

              (a)

            union parse_hdr {
             struct hdr {
              char data1;
              char data2;
              int data3;
              int data4;
            } hdr;
            struct tkn {
             int class;
             long tag;
            } tkn;
            } parse_item;

              (b)

            char *ptr = msgbuf;

            parse_item.hdr.data1 = *ptr++;
            parse_item.hdr.data2 = *ptr++;
            parse_item.hdr.data3 = (*ptr++ << 8 | *ptr++);
            parse_item.hdr.data4 = (*ptr++ << 8 | *ptr++);

            if (parse.tkn.class >= MIN_TOKEN_CLASS && parse.tkn.class <= MAX_TOKEN_CLASS) {
             interpret_tag(parse.tkn.tag);
            }

              在第一個情況中(圖2),tag域不是像期望的那樣線性拉長,而被填充了一些垃圾。而在第二個情況中(圖3),無論是class還是tag域,都不再有意義,兩個char值因為被打包成一個int,所以也都不再正確。再次強調(diào),首先不要假設(shè)標準數(shù)據(jù)類型大小一樣,其次還要了解它們是怎樣被映射成其他數(shù)據(jù)類型的,這才是書寫可移植代碼的最好方法。

              機器尋址特性

              幾乎所有的處理器都在字邊界上以字為單位進行尋址,而且通常都為此作了一些優(yōu)化。另有一些的處理器允許其他類型的尋址,如以字節(jié)為單位尋址、或在半個字邊界上以半字為單位尋址,甚至還有一些處理器有輔助硬件允許在奇數(shù)邊界上同時以字和半字進行尋址。

              尋址機制在不同計算機上會有所變化,最快的尋址模式是在字邊界上以字為單位進行尋址。其他方式的尋址需要輔助硬件,通常都會對內(nèi)存訪問增加了一些時鐘周期。而這些過多的模式和特殊硬件的支持,是與RISC處理器的設(shè)計初衷背道而馳的,就拿克雷計算機來說,就只支持在字邊界上以字為單位進行尋址。

              在那些不提供多種數(shù)據(jù)類型尋址方式的計算機上,編譯器可以提供一些模擬。例如:編譯器可以生成一些指令,當讀取一個字時,通過移位和屏蔽,來找到所想要的位置,以此來模擬在字中的半字尋址,但這會需要額外的時鐘周期,并且代碼體積會更大。

              從這點上來說,位域的效率是非常低的,在以位域來取出一個字時,它們產(chǎn)生的代碼體積最大。當你存取同一個字中的其他位域時,又需要對包含這個位域字的內(nèi)存,重新進行一遍尋址,這就是典型的以空間換時間。

              當在設(shè)計數(shù)據(jù)結(jié)構(gòu)時,我們總是想用可以保存數(shù)據(jù)的最小數(shù)據(jù)類型,來達到節(jié)省空間的目的。我們有時小氣得經(jīng)常使用char和short,來存取位特域,這就像是為了節(jié)省一角錢,而花了一元錢。儲存空間上的高效,會付出在程序速度和體積上隱藏的代價。

              試想你只為一個緊湊結(jié)構(gòu)分配了一小點的空間,但卻產(chǎn)生了大量的代碼來存取結(jié)構(gòu)中的域,而且這段代碼還是經(jīng)常執(zhí)行的,那么,會因為非字尋址,而導致代碼運行緩慢,而且充斥了大量用于提取域的代碼,程序體積也因此增大。這些額外代碼所占的空間,會讓你前面為節(jié)省空間所做的努力付之東流。

              在高級數(shù)據(jù)結(jié)構(gòu)中,特定的比特定位已不是必須的了,應(yīng)在所有的域中都使用字(word),而不要操心它們所占用的空間。特別是在程序某些依賴于機器的部分,應(yīng)該為字準備一個typedef,如下:

            /*在這臺計算機上,int是一個字長*/
            typedef word int;

              在高級結(jié)構(gòu)中,對所有域都使用字有如下好處:

              a.. 對其他計算機架構(gòu)的可移植性

              b.. 編譯器可能生成最快的代碼

              c.. 處理器可能最快地訪問到所需內(nèi)存

              d.. 絕對沒有結(jié)構(gòu)對齊的意外發(fā)生

              必須也承認,在某些時候,是不能做到全部使用字的。例如,有一個很大的結(jié)構(gòu),但不會被經(jīng)常存取,如果使用了數(shù)千個字的話,體積將會增大25%,但使用字通常會節(jié)省空間、提高執(zhí)行速度,而且更具移植性。

              以下是我們的結(jié)論:

              書寫跨平臺移植的代碼,其實是件簡單的事情。最基本的規(guī)則是,盡可能地隱藏機器字長的細節(jié),用非常精確的數(shù)據(jù)元素位大小來映射物理數(shù)據(jù)結(jié)構(gòu)。或者像前面所建議的,為高級編程使用高級數(shù)據(jù)結(jié)構(gòu),而低級編程使用低級數(shù)據(jù)結(jié)構(gòu),當闡明高級數(shù)據(jù)結(jié)構(gòu)時,對標準C的標量類型,不要作任何假設(shè)

            posted on 2006-07-07 13:51 井泉 閱讀(285) 評論(0)  編輯 收藏 引用 所屬分類: c軟件工程

            久久久久国产一区二区| 久久婷婷五月综合国产尤物app | 美女写真久久影院| 久久久久久久尹人综合网亚洲| 国产精品一区二区久久| 国产精品美女久久久久av爽| 色播久久人人爽人人爽人人片aV| 久久久精品久久久久久| 日本人妻丰满熟妇久久久久久| 亚洲国产二区三区久久| 亚洲精品成人久久久| 亚洲国产精品一区二区久久hs| 久久99国产精一区二区三区| 一级A毛片免费观看久久精品| 久久电影网一区| 囯产极品美女高潮无套久久久 | 国内精品久久久久久久亚洲| 偷偷做久久久久网站| 久久www免费人成看国产片| 亚洲AV日韩精品久久久久久| 久久久久人妻精品一区三寸蜜桃| 久久无码人妻一区二区三区| 77777亚洲午夜久久多喷| 精品乱码久久久久久夜夜嗨 | 久久久久亚洲精品无码蜜桃| 欧美精品丝袜久久久中文字幕 | 久久久久久久人妻无码中文字幕爆 | 欧美日韩精品久久久免费观看| 青青草国产成人久久91网| 久久精品国产第一区二区三区| 亚洲中文字幕伊人久久无码| 久久国产精品一区| 久久香蕉国产线看观看乱码| 99999久久久久久亚洲| 久久久无码精品亚洲日韩按摩 | 久久这里只有精品久久| 97久久精品国产精品青草| 久久精品中文騷妇女内射| 伊人久久久AV老熟妇色| 久久精品人人槡人妻人人玩AV | 日日噜噜夜夜狠狠久久丁香五月|