一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

C 編程的 42 條建議(五)

 刀首木 2017-03-17

31. 在 C 和C++中,數(shù)組不是按值傳遞的


下面的代碼來自游戲'Wolf'.代碼中包含的錯誤被 PVS-Studio診斷為:V511在'sizeof (src)'這個表達式中,sizeof()返回的是指針的大小,而不是數(shù)組的大小。


解釋


有時候,程序員會忘記在C/C++中,你不可以把一個數(shù)組值傳遞給一個函數(shù)。因為傳數(shù)組給一個函數(shù),數(shù)組類型自動轉(zhuǎn)換為指針類型。方括號里里的數(shù)字沒什么意思,它們只是用來告訴程序員,多大的數(shù)組被傳進去了。事實上,你可以傳任意大小的數(shù)組。比如,下面的代碼也能編譯成功:


void F(int p[10]) { }

void G()

{

  int p[3];

  F(p);

}


類似的,sizeof(src) 不是計算數(shù)組的大小,而是指針的大小。然后結(jié)果就是,memcpy() 只是復制了一部分數(shù)組而已。也就是,4或者8字節(jié),這都取決于指針的大?。ú凰闫婀值慕Y(jié)構(gòu))。


正確代碼


最簡單的修正是這樣的:


ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {

  memcpy(mat, src, sizeof(float) * 3 * 3);

}


建議


有多種方法可以讓你的代碼更安全。


知道數(shù)組大小。你可以在函數(shù)中用數(shù)組的引用做參數(shù)。但并不是所有人都知道可以這么做。甚至更少的人知道怎么寫。所以我希望下面的例子能夠有用,有趣:


ID_INLINE mat3_t::mat3_t( float (&src)[3][3] )

{

  memcpy( mat, src, sizeof( src ) );

}


現(xiàn)在,就可以在傳遞數(shù)組的時候只用右邊的部分。而且最重要的是,sizeof()計算的是數(shù)組的大小了,不再是一個指針的。


解決這個問題的另一個方法是用std::array類。


不知道數(shù)組大小。有些編程書的作者建議使用std::vector類和其他相似的類。但是,這實際應用做,并不總是那么方便。


有時,你也會想要用指針來解決這個問題。在這個例子中,你可以傳兩個參數(shù)給那個函數(shù):一個指針,元素個數(shù)。然而,一般來說,這樣做都不太好,因為它會導致很多bug。


在這樣的例子中,'C++ Core Guidelines'這篇文章里的觀點蠻有用的。我建議讀'Do not pass an array as a single pointer'。總的來說,在你有空的時候讀'C++ Core Guidelines'真的不失為一件有益的事。它里面包含了很多有用的觀點。


32. 危險的 printf


下面的代碼選自 TortoiseSVN 項目。代碼中包含的錯誤被 PVS-Studio 診斷為:V618 以一種危險的方式調(diào)用 printf 函數(shù),因為傳遞進去的那一行應該包含格式化說明。安全使用 printf 的例子:: printf('%s', str);


BOOL CPOFile::ParseFile(....)

{

  ....

  printf(File.getloc().name().c_str());

  ....

}


解釋


當你打算打印或者,比如說,寫一個字符串到文件中,很多程序員會這樣寫:


printf(str);

fprintf(file, str);


一個優(yōu)秀的程序員應該時刻記得,這樣組織代碼是非常不安全的。事情是這樣的,如果格式化說明不知怎樣進入一個字符串里,將會導致無法預測的后果。


讓我們回過頭來看最初的例子。如果文件名是'file%s%i%s.txt',那么這個程序會崩潰,或者輸出一些不知所云的東西。但這不還是問題的全部。事實上,這樣的函數(shù)調(diào)用是一個真正的漏洞。在它的幫助下,我們能發(fā)動攻擊。用一些刻意的字符串,就可以打印內(nèi)存里的私有數(shù)據(jù)。


更多關(guān)于這個漏洞的信息可以查看這篇文章?;c時間去通讀一遍,我保證,會很有趣的。你不僅能看到理論基礎(chǔ),還可以看到實際的例子。


正確代碼


printf('%s', File.getloc().name().c_str());


建議


像 printf() 這樣的函數(shù)會引起很多有關(guān)安全的問題。最好一點也不要使用它們,你可以用其他的來代替啊。比如說,你會發(fā)現(xiàn) boost::format 或者 std::stringstream 也很有用。


一般來說,草率地使用 printf(), sprintf(), fprintf(),等等函數(shù)不僅會導致運行不當,而且會引發(fā)潛在的漏洞,然后別人就可以利用這個漏洞來攻擊你。


33. 永遠不要間接引用空指針


bug 是在 GIT 的源代碼中發(fā)現(xiàn)的。代碼中包含的錯誤被 PVS-Studio 診斷為:V595 在還沒有驗證‘tree‘指針是否為空之前就使用它。檢查134,136行。


void mark_tree_uninteresting(struct tree *tree)

{

  struct object *obj = &tree->object;

  if (!tree)

    return;

  ....

}


解釋


無疑,這是一個糟糕的做法,因為它間接引用了空指針,而這樣間接引用的結(jié)果就是未定義行為。我們都同意這背后的理論基礎(chǔ)。


但是,當具體運用的時候,程序員們就開始爭論不休了??傆腥寺暦Q,這段代碼能夠正確運行。他們甚至以項上人頭做擔?!獙λ麄儊碚f,它也總是能運行。所以,我要給出更多的理由來證明我的觀點。這就是為什么這篇文章是改變他們觀點的又一嘗試。


我故意選了這么一個能夠引發(fā)更多討論的例子。當tree指針被調(diào)用,類成員不僅是在使用,也在計算該成員的地址。那么,如果(tree == nullptr),就永遠不會用到成員的地址,而且函數(shù)已經(jīng)退出了。很多人都認為這段代碼是正確的。


但并不是。你不應該這么寫代碼。未定義行為不一定造成程序崩潰,比如賦值給空地址,或者諸如此類的行為。只要你調(diào)用了一個等于null的指針,未定義行為可以是任何操作。這個時候再討論這段代碼會如何運行已經(jīng)沒有意義了,因為此時它可以做它任何想做的操作。


未定義行為的一個標志是,編譯器會把'if (!tree) return;'刪掉——編譯器看到指針已經(jīng)被調(diào)用了,而指針不是空的,那么這一行檢查就會被編譯器移除。這只是眾多版本中的一個,而這個版本會引起程序崩潰。


我建議閱讀這一篇文章:http://www./en/b/0306/,里面給出了更多細節(jié)。


正確代碼


void mark_tree_uninteresting(struct tree *tree)

{

  if (!tree)

    return;

  struct object *obj = &tree->object;

  ....

}


要注意未定義行為,即使一切看上去都沒什么問題。沒必要冒險。即使我已經(jīng)寫了,但還是很難表現(xiàn)出它的價值。嘗試著去避免未定義行為,即使一切看上來都沒問題。


有人會想,他清楚的知道,未定義行為是怎樣運作的。而且,他可能會想,這意味著,他可以做一些其他人不能做的事,還能保證代碼不出錯。但并非如此。下一章節(jié)將會說明未定義行為真的非常危險。


34. 未定義行為比你想象的要貼近我們的生活


這次很難給出實際應用的例子。但是,我經(jīng)常有看到會導致下面要所描述問題的可疑代碼。這個錯誤是在處理大數(shù)組的時候出現(xiàn)的,而我不知道哪個項目會用到那么大的數(shù)組。我們沒有真的收集到64位的錯誤,所以今天的例子是刻意的。


讓我們來看一段刻意出錯的代碼:


size_t Count = 1024*1024*1024; // 1 Gb

if (is64bit)

  Count *= 5; // 5 Gb

char *array = (char *)malloc(Count);

memset(array, 0, Count);

 

int index = 0;

for (size_t i = 0; i != Count; i++)

  array[index++] = char(i) | 1;

 

if (array[Count - 1] == 0)

  printf('The last array element contains 0.\n');

 

free(array);


解釋


如果你構(gòu)建的是這個項目的32位版本,代碼是可以正確運行的。但是如果我們要編譯64位版本,情況就會變得很復雜。


這個64位項目開始的時候申請5GB的緩沖區(qū),然后初始化為0.接著,用循環(huán)修改為非0值:用“|1”來保證非0.


現(xiàn)在來猜一下,如果在x64位版本下用Visual Studio 2015編譯的時候,這段代碼會如何運行?你有答案嗎?如果有,那我們繼續(xù)。


如果你運行的是這個項目的調(diào)試版本,它會因為下標溢出而崩潰。在一定程度上,下標會溢出,而且其值會變成?2147483648 (INT_MIN).


聽上去很有邏輯,對不對?事情并不是這樣的。這是一個未定義行為,任何事情都有可能發(fā)生。


為獲得更多內(nèi)容,我推薦下面的鏈接:



好玩的是——當我或者其他人說這段代碼會引發(fā)未定義行為的時候,就會有人抱怨。我不知道為啥,但看起來好像是,他們覺得自己對C++有絕對的了解,而且知道編譯器是怎樣運作的。


但事實上他們都沒有注意到它。如果他們知道,他們不會這么說的(大眾的觀點):


這在理論上沒什么意義。好吧,是,‘int’溢出會導致未定義行為。但是這沒什么,不過老生常談而已。在實際應用中,我們知道代碼運行后能得到什么。1 加 INT_MAX 等于 INT_MIN 嘛。但是,可能在宇宙中的某一個角落存在著能讓這種情況(整形一出也能得我們想要的結(jié)果)發(fā)生的結(jié)構(gòu),但就是我的 Visual C++/GCC 沒能給出正確的結(jié)果而已。


現(xiàn)在,沒有任何魔法。我會用一個簡單的例子來證明未定義行為,而且沒有用到什么奇怪的結(jié)構(gòu),只是一個 win64 的項目。


把上面的例子構(gòu)建成發(fā)布版本并運行就已經(jīng)足夠了。代碼會崩潰結(jié)束,而且“最后一個數(shù)組元素包含0”的提示也不會出現(xiàn)。


未定義行為一般以如下的方式出現(xiàn)。所有的數(shù)組元素都會被賦值,盡管數(shù)組下標的類型 int 并不足以覆蓋所有的元素。那些至今還在懷疑我的人,可以看一下下面的代碼:

 

 for (size_t i = 0; i != Count; i++)

000000013F6D102D  xor       ecx,ecx  

000000013F6D102F  nop  

    array[index++] = char(i) | 1;

000000013F6D1030  movzx       edx,cl  

000000013F6D1033  or          dl,1  

000000013F6D1036  mov      byte ptr [rcx+rbx],dl  

000000013F6D1039  inc         rcx  

000000013F6D103C  cmp         rcx,rdi  

000000013F6D103F  jne         main+30h (013F6D1030h)


這里就有一個未定義行為。而且沒有用到什么特殊的編譯器,用的就是VS2015.


如果你用 unsigned 代替 int,就不會有未定義行為。而數(shù)組就只有一部分被填充,最后我們會收到一條信息——“最后一個數(shù)組元素包含0”。


相似的代碼用 unsigned 的情況:


unsigned index = 0;

000000013F07102D  xor         r9d,r9d  

  for (size_t i = 0; i != Count; i++)

000000013F071030  mov         ecx,r9d  

000000013F071033  nop         dword ptr [rax]  

000000013F071037  nop         word ptr [rax+rax]  

    array[index++] = char(i) | 1;

000000013F071040  movzx       r8d,cl  

000000013F071044  mov         edx,r9d  

000000013F071047  or          r8b,1  

000000013F07104B  inc         r9d  

000000013F07104E  inc         rcx  

000000013F071051  mov         byte ptr [rdx+rbx],r8b  

000000013F071055  cmp         rcx,rdi  

000000013F071058  jne         main+40h (013F071040h)


正確代碼


在程序中你要用對正確的類型,這樣才能確保它順利運行。如果你要處理大數(shù)組,忘了 int 和 unsigned 吧。正確的類型有 ptrdiff_t, intptr_t, size_t, DWORD_PTR, std::vector::size_type 等等,在這里用 size_t.


size_t index = 0;

for (size_t i = 0; i != Count; i++)

  array[index++] = char(i) | 1;


建議


如果根據(jù) C/C++ 的語言機制會導致未定義行為的話,就別跟它們爭了,也不要嘗試去預測它們會在將來做出什么表現(xiàn)。只要不寫危險的代碼就好了啊。


還是有很多固執(zhí)的程序員不愿意在轉(zhuǎn)換負數(shù)、比較 this 和 null 或者有符號類型溢出時看任何表示懷疑的言論。


不要這樣。就算現(xiàn)在代碼運行得好好的也不意味著一切都是好的。未定義行為是不可預測的??深A測的程序行為是未定義行為的一個變體。


35. 在枚舉中加了新的枚舉常量后別忘了修改switch運算


下面的代碼來自 Appleseed 項目。代碼中包含的錯誤被 PVS-Studio 診斷為:V719 switch 語句沒有覆蓋枚舉“InputFormat”的所有值,少了InputFormatEntity.


enum InputFormat

{

    InputFormatScalar,

    InputFormatSpectralReflectance,

    InputFormatSpectralIlluminance,

    InputFormatSpectralReflectanceWithAlpha,

    InputFormatSpectralIlluminanceWithAlpha,

    InputFormatEntity

};

 

switch (m_format)

{

  case InputFormatScalar:

    ....

  case InputFormatSpectralReflectance:

  case InputFormatSpectralIlluminance:

    ....

  case InputFormatSpectralReflectanceWithAlpha:

  case InputFormatSpectralIlluminanceWithAlpha:

    ....

}


解釋


有的時候我們需要在已經(jīng)存在的枚舉里加入新的元素,當我們做這個操作的時候,我們需要特別的謹慎——因為我們要檢查全部代碼看看哪里有用到這個枚舉,比如說在 switch 和 if 中。上面給出的代碼就是這種情況。


在 InputFormat 中加了 InputFormatEntity——這里是我想象的,因為確實在 InputFormat 的后面有加入這個常量。很多時候,程序員在枚舉后面加了新的常量,然后忘了檢查代碼以確保他們有正確處理這個常量,也沒有修改 switch 操作。


最后的結(jié)果就是,在這個例子中,并沒有處理到'm_format==InputFormatEntity'的情況。


正確代碼


switch (m_format)

{

  case InputFormatScalar:

  ....

  case InputFormatSpectralReflectance:

  case InputFormatSpectralIlluminance:

  ....

  case InputFormatSpectralReflectanceWithAlpha:

  case InputFormatSpectralIlluminanceWithAlpha:

  ....

  case InputFormatEntity:

  ....

}


建議


讓我們想想,怎樣在代碼重構(gòu)的時候避免這種錯誤?最簡單,但不那么有效的解決方法就是加一個‘default’,它可以輸出一個信息,像這樣:


switch (m_format)

{

  case InputFormatScalar:

  ....

  ....

  default:

    assert(false);

    throw 'Not all variants are considered'

}


現(xiàn)在,如果變量 m_format 是 InputFormatEntity,我們就可以看到一個異常。這樣的處理方法有兩個不好的地方:


  1. 因為有可能在測試階段這個錯誤并沒有顯現(xiàn)出來(如果在測試的時候,m_format不等于InputFormatEntity),那這個錯誤就會流入發(fā)布版本,以后才會顯現(xiàn)——在客戶運行時出現(xiàn)。如果要顧客來反映這種問題,真的很糟糕。

  2. 如果我們把default考慮為一個錯誤,那我們就不得不寫一個case來解決枚舉所有可能的值。這樣就很不方便,尤其是當一個枚舉中有很多常量的時候。有時,用 default 來處理不同 case 真的很方便。


我建議用以下的方法來解決這個問題,我不敢說這個方法很完美,但至少它有解決到問題。


當你定義一個枚舉的時候,要確保你也加了一條特殊的注釋。你也可以用關(guān)鍵詞和枚舉名。


例子:


enum InputFormat

{

  InputFormatScalar,

  ....

  InputFormatEntity

  //If you want to add a new constant, find all ENUM:InputFormat.

};

 

switch (m_format) //ENUM:InputFormat

{

  ....

}


在上面的代碼中,當你要改變枚舉InputFormat,你就可以直接在項目的源代碼中查找“ENUM:InputFormat”。


如果你是在一個開發(fā)者團隊中,你可以告訴你的小伙伴們這個約定,然后把它加入到你們的編程標準和風格指引中。如果有人沒能遵守這條原則,真遺憾。


原文鏈接:https://software.intel.com/en-us/articles/the-ultimate-question-of-programming-refactoring-and-everything
本文由 看雪翻譯小組 lumou 編輯


相 關(guān) 閱 讀:


C++編程的 42 條建議(二)

C++編程的 42 條建議(三)

C++編程的 42 條建議(四)

....

更多優(yōu)秀文章點擊左下角“關(guān)注原文”查看!

看雪論壇:http://bbs./

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    亚洲国产av精品一区二区| 亚洲av秘片一区二区三区| 日韩精品毛片视频免费看 | 尤物久久91欧美人禽亚洲| 国产成人精品99在线观看| 偷拍洗澡一区二区三区| 人妻少妇系列中文字幕| 美女激情免费在线观看| 色涩一区二区三区四区| 色欧美一区二区三区在线| 国产精品免费福利在线| 午夜福利视频偷拍91| 免费在线播放不卡视频| 国产高清三级视频在线观看| 99精品人妻少妇一区二区人人妻| 国产成人精品在线一区二区三区| 欧美国产日本免费不卡| 永久福利盒子日韩日韩| 亚洲欧美日韩另类第一页| 色婷婷丁香激情五月天| 亚洲欧美日韩精品永久| 老司机精品线观看86| 清纯少妇被捅到高潮免费观看| 91精品日本在线视频| 国产不卡免费高清视频| 中国美女草逼一级黄片视频| 在线亚洲成人中文字幕高清| 亚洲女同一区二区另类| 久久香蕉综合网精品视频| 日韩美成人免费在线视频| 日韩精品成区中文字幕| 国产在线一区二区免费| 在线亚洲成人中文字幕高清| 欧美大胆女人的大胆人体| 亚洲人妻av中文字幕| 好吊妞在线免费观看视频| 黄色激情视频中文字幕| 伊人久久青草地综合婷婷| 激情亚洲一区国产精品久久| 日韩国产传媒在线精品| 亚洲国产欧美精品久久|