是誰在愚弄程式員?

來源:互聯網
上載者:User

        今天參加了一個小小的筆試,試卷上有一些模稜兩可的指標問題。回來做了測試,感覺結果很古怪,就去查了源碼。感覺大失所望。為什麼庫函數的設計如此不合理呢?原來我們每天遵循著不合理的規則,出現了錯誤卻時常埋怨自己。到底是誰的錯呢?是誰在愚弄程式員呢?下面說說我遇到的問題,及庫函數的一些問題,希望你能從中得到答案.。

今天的筆試題可以說全部是指標問題。總體感覺下來,體量太小,題目也比較簡單,考不出真正的實力.不過經過仔細的分析,小題中卻蘊含著大問題.下面我們看看其中兩道我認為不太合理的題目:void test(){    char a[10],b[10];    int i;    for(i = 0; i < 10; ++i)    {       b[i] = 'a';    }    strcpy(a,b);}指出程式中的錯誤:剛開始一看,我就懵了.正讓我從何說起呢。我們仔細看這個程式,它的不合理的地方是我們應該在數組b的最後一個位置放上一個'/0',這樣才凸顯合理性.然而程式中並沒有放,我們也不能說他是錯誤吧.現在我們繼續看程式:strcpy(a,b);把數組b裡面的內容複寫到a中.很明顯這裡有問題.因為我們在數組b中沒有放置'/0',我們根本不知道數組b到底有多長.也就是說我們根本就無法知道數組a到底能不能放下數組b中的內容,並且很大很能是放不下的.也就是說數組a的空間太小了。那數組a該申請多大的空間呢?不知道.那麼說這道題的錯誤該說是什麼呢?經過這樣的分析好像我們說數組中應該放一個'/0'.不過仔細想想缺少一個'/0'也該不算是一個錯誤把.呵呵…所以我認為這道題不是太好.void test(const char* str){    char a[10];    int i;    if (strlen(str) <= 10)     {       strcpy(a,str);        }}指出上面程式中的錯誤:經過仔細的分析後,你可以看看上面的程式,你確定你找出所有的錯誤了嗎?我給出的答案是:將 <= 換成 < .你的答案是不是也是如此呢?如真是這樣,那我們真是同病相憐啊.好的,廢話不多說了,讓我們一起看看這個程式的問題吧:1: 你信任參數指標str嗎?    其實你不該信任它。我們看看他會是什麼:    ①:數組 ②: 正常有值的指標 ③: NULL指標 ④: 野指標.哇,沒想到吧.它可以是這麼多東西.恐怖嗎?別怕,下面我們分別看看這幾種情況:①: 數組如果是數組,並且該數組正常有值的話,那麼他有兩種情況,第一,有’/0’.那整個程式就沒問題了,我們不必修改任何地方.第二,沒有’/0’.那麼我可以說你慘了,無論我們怎麼修改程式,都無法根本的解決問題了。為什麼呢?如果一個字元數組中存有值,並且沒有’/0’,那麼我們根本不知道它有多大。即我們使用strlen()函數測出來的值一般不會是一個正確的值.那麼我們怎麼去修改程式也無法解決問題了。我們可以說這不是這一個函數該解決的問題,是程式員的問題了,我們傳參時應該傳遞正確的參數.②: 正常有值的指標.這個問題很好解決,一般編譯器會自動為我們的指標末尾加上一個’/0’。不過我們若是使用strlen()測試的話,’/0’是不會被包括在內的.所以此時我們應該為’/0’預留出一個空間.因此程式只需將<= 改為 < 就OK了.③: NULL指標若是遇到這個問題,它可以說是一個程式員的問題,也可以說是函數設計的問題.首先我們不應該將一個NULL指標傳遞給一個函數.這是一種不正確的行為.在說該函數的設計,它應該考慮到傳遞NULL指標這種情況,並且該種情況也很容易處理.只需:if (str == NULL){    return;}④: 野指標遇到這種情況,你就投降吧.你無論如何設計函數,都無法解決問題的.這絕對是一個程式員的問題.這就是為什麼很多經典的書籍都反覆強調:在我們定義一個指標時,如果不立即使用.最好給它初始化為NULL,如: char *p = NULL;…在你使用完一個指標後,如果它是堆積指標,一定要給他釋放,目的是為了防止記憶體流失.但在我們釋放完記憶體後千萬別忘了給它賦值為NULL。以防止它變成野指標被誤用.如:char *p = (char*)malloc(16);…free(p);p = NULL;好了,至此四種情況我們都討論完了,你說這個程式我們該如何修改呢?我想比較合適一點的答案是:程式開始增加:if (str == NULL){    return;}然後將<= 改為 < .分析其他無法處理的情況,應該是程式員的問題了.不屬於該函數管理的範疇.好了,說完我面試的兩道小題,下面看看我們的庫函數吧.我的庫函數是在VC2008的安裝資料夾裡的一個src檔案夾裡找到的.裡面有很多我們常用的庫函數的源碼.聽到這裡我想你一定垂涎雨滴,很想去看看大師們設計的庫函數吧.是否是很想好好的瞻仰與模仿一番啊?先別,等我說完,我相信你的這個慾望就不會那麼強烈了.1 strlen函數.我們有一個小問題引出這個函數吧:#include <stdio.h>#include <string.h>void main(){    char a[10];    printf("%d/n",strlen(a));}大家猜猜這個程式的結果該是多少.在我的機器上每次輸出的結果都是3.奇怪嗎?其實如果你看了庫函數就不覺得奇怪了:size_t __cdecl strlen (        const char * str        ){        const char *eos = str;         while( *eos++ ) ;         return( eos - str - 1 );}由while( *eos++ ) ;我們可以看出該函數是查到’/0’才結束的.而我們剛申請的數組a[10],由於我們並沒有初始化,所以它裡面還存有原來的值.裡面可能有’/0’也可能沒有.所以用strlen輸出a的長度是隨即的.不信我們可以做一個實驗,改變a[10]中原有記憶體值,使strlen(a)輸出一致結果.#include <stdio.h>#include <string.h>void SetMemory(){    char a[100];    int i;    for (i = 0; i < 100; ++i)    {       a[i] = 0;    }}void test(){    char a[10];    printf("%d/n",strlen(a));}int main(int argc, char *argv[]){    SetMemory();    test();    return 0;}請大家在C編譯器上運行此問題,答案為0.VC++6.0上我實驗了不行.從strlen函數源碼中我們也可以看出它返回的長度不包括’/0’。真不知這種尋找’/0’定長度的設計方法好不好.反正很多時候它會帶給我們很多煩惱,不過除了這種設計沒有其他更好的方法了.有的同學可能會說(其實我原來也這麼想):那數組有多長他就給返回多長不就行了.好的如果按照這正想法設計我們看看需要做些什麼:首先在設計函數時需要分兩部分,一個是數組,一個是指標.指標的時候又可以分為堆積指標和棧指標。堆積指標我們需要記錄它申請的長度,然後返回。棧指標也就是說我們僅僅定義了一個指標還沒有給他分配空間,那麼我們該範圍它的長度為多少呢?按0算吧.好的,那麼現在我們去看數組,你說一個數組我們該如何知道它的長度呢,只有記錄,單憑記憶體是看不出來的.我們需要記錄數組申請時的長度.然後在strlen中尋找返回就是了.好的,我們看看這樣的設計好嗎?我想是不錯的,不過就是多佔用了一些空間記錄數組的長度,而指標的申請空間長度已經在記錄了.其實庫函數的設計還有一個不合理的地方,那就是他不能處理NULL指標.並且如何你不小心傳進去了一個NULL指標.很可能會出現記憶體錯誤.鬱悶吧?如果我們不能看到庫函數的實現,那麼我們也就不知道它能處理那些東西。那麼我們就不會認為這樣很正常的使用是錯誤的,那麼如果讓我們在一個大程式裡尋找這樣一個記憶體錯誤將是一件很困難的事情.對此微軟推出了另一個庫函數:size_t __cdecl strnlen(const char *str, size_t maxsize){    size_t n;     /* Note that we do not check if s == NULL, because we do not     * return errno_t...     */     for (n = 0; n < maxsize && *str; n++, str++)        ;     return n;}對於這個比上一個合理了很多,安全了很多,至少在你正確的傳進maxsize的情況下他不會越界訪問,但感覺還是有點怪怪的.2 strncpychar * __cdecl strncpy (        char * dest,        const char * source,        size_t count        ){        char *start = dest;         while (count && (*dest++ = *source++))    /* copy string */                count--;         if (count)                              /* pad out with zeroes */                while (--count)                        *dest++ = '/0';         return(start);}由於無法找到strcpy的源碼,只能去看這個了.仔細看這個庫函數,你會發現一個情況:       if (count)                              /* pad out with zeroes */                while (--count)                        *dest++ = '/0';這句基本上執行不到.其中的count參數我們一般傳進去的都是 <= strlen(source)+1;(包括了‘/0’)也就是說count總是在遇到’/0’之前或是同時也減為0了.那麼上面的代碼也就無法執行了,除非誰傻到傳一個大於strlen(source)+1的值進去,那麼上面代碼就可以發揮作用了。上面代碼沒有執行也就意味著我們得到的字串中沒有’/0’,後果我們可想而知.據說是為了安全才在原來strcpy的基礎上加了一個參數count,但從這裡並沒有看到count起到了什麼作用,並且庫函數應該為dest末尾加上’/0’.這應該屬於它的職責,如果把這個也交給程式員,那就太令人噁心了…好了就說這麼多把。昨天看了這兩個庫函數有些失望,其他的還沒看.說句實在話,其實並不是指標難學,而是我們不瞭解很多的實現與記憶體的原理,如果瞭解了,那麼指標和一些文法的東西並沒有什麼差別,所以我建議大家在學習指標的時候多多去看看庫函數,去瞭解一下記憶體的原理.這樣會對你協助很多.看了這兩個庫函數感覺有些失望,知不知道很多的記憶體錯誤和不明的結果是應該埋怨程式員還是該埋怨庫函數的設計不合理.不管怎麼樣,作為一個客戶我們只能去使用了,畢竟沒掏錢,呵呵…還是安心的去瞭解人家的庫,遵守人家的規則吧.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.