新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 牛人業(yè)話(huà) > C語(yǔ)言的那些小秘密之?dāng)嘌?/p>

C語(yǔ)言的那些小秘密之?dāng)嘌?/h1>
作者: 時(shí)間:2015-04-04 來(lái)源:網(wǎng)絡(luò) 收藏

  每次寫(xiě)摘要我都覺(jué)得是一件很頭疼的事兒,因?yàn)槲抑勒娴暮苤匾?,它幾乎直接就決定了讀者的數(shù)量。可能花了九六二虎之力寫(xiě)出來(lái)的東西,因?yàn)檎氖《肮ΡM棄,因?yàn)榻^大多數(shù)的讀者看文章之前都會(huì)瀏覽下摘要,如果他們發(fā)現(xiàn)摘要“不對(duì)口”,沒(méi)有什么特色和吸引人的地方,那么輕則采用一目十行的方法看完全文,重則對(duì)文章判“死刑”,一篇文章的好壞雖然不能用摘要來(lái)衡量,但是它卻常常被讀者用來(lái)衡量一篇文章的好壞,從而成為了文章讀者數(shù)量多少的一個(gè)關(guān)鍵因素。下面言歸正傳來(lái)說(shuō)說(shuō),如果出于一般性的學(xué)習(xí),應(yīng)付考試的話(huà),我想很少有人會(huì)在代碼中使用,可能有的人在此之前從來(lái)沒(méi)有使用過(guò)。那么斷言的使用到底能給我們的代碼帶來(lái)什么呢?我盡可能的把我所理解的斷言的使用講解清楚,希望我在此所講的斷言能夠?qū)δ阌兴鶐椭?,讓你以后能夠在代碼中靈活使用斷言。

本文引用地址:http://2s4d.com/article/272070.htm

  在講解之前,我們先來(lái)對(duì)斷言做一個(gè)基本的介紹,讓大家對(duì)斷言有一個(gè)大致的了解。在使用編寫(xiě)工程代碼時(shí),我們總會(huì)對(duì)某種假設(shè)條件進(jìn)行檢查,斷言就是用于在代碼中捕捉這些假設(shè),可以將斷言看作是異常處理的一種高級(jí)形式。斷言表示為一些布爾表達(dá)式,程序員相信在程序中的某個(gè)特定點(diǎn)該表達(dá)式值為真??梢栽谌魏螘r(shí)候啟用和禁用斷言驗(yàn)證,因此可以在測(cè)試時(shí)啟用斷言,而在部署時(shí)禁用斷言。同樣,程序投入運(yùn)行后,最終用戶(hù)在遇到問(wèn)題時(shí)可以重新起用斷言。它可以快速發(fā)現(xiàn)并定位軟件問(wèn)題,同時(shí)對(duì)系統(tǒng)錯(cuò)誤進(jìn)行自動(dòng)報(bào)警。斷言可以對(duì)在系統(tǒng)中隱藏很深,用其它手段極難發(fā)現(xiàn)的問(wèn)題可以用斷言來(lái)進(jìn)行定位,從而縮短軟件問(wèn)題定位時(shí)間,提高系統(tǒng)的可測(cè)性。實(shí)際應(yīng)用時(shí),可根據(jù)具體情況靈活地設(shè)計(jì)斷言。

  通過(guò)上面的講解我們對(duì)于斷言算是有了一個(gè)大概的了解,那么接下來(lái)我們就來(lái)看看中assert宏在代碼中的使用。

  原型定義:

  void assert( int expression );

  assert宏的原型定義在中,其作用是先計(jì)算表達(dá)式 expression ,如果expression的值為假(即為0),那么它先向stderr打印一條出錯(cuò)信息,然后通過(guò)調(diào)用abort 來(lái)終止程序運(yùn)行。

  下面來(lái)看看一段代碼:

  #include

  #include

  int main( void )

  {

  int i;

  i=1;

  assert(i++);

  printf("%dn",i);

  return 0;

  }

  運(yùn)行結(jié)果為:

  

 

  看看運(yùn)行結(jié)果,因?yàn)槲覀兘o定的i初始值為1,所以使用assert(i++);語(yǔ)句的時(shí)候不會(huì)出現(xiàn)錯(cuò)誤,進(jìn)而執(zhí)行了i++,所以其后的打印語(yǔ)句輸出值為2。如果我們把i的初始值改為0,那么就回出現(xiàn)如下錯(cuò)誤。

  Assertion failed: i++, file E:fdsaassert2.cpp, line 8

  Press any key to continue

  是不是發(fā)現(xiàn)根據(jù)提示很快就能定位出錯(cuò)點(diǎn)呢?!既然assert這么便于定位出錯(cuò)點(diǎn),看來(lái)的確我們有必要熟練的在代碼中使用它,但是什么東西的使用都是有規(guī)則的,assert的使用也不例外。

  斷言語(yǔ)句不是永遠(yuǎn)會(huì)執(zhí)行,可以屏蔽也可以啟用,這就要求assert不管是在屏蔽還是啟用的情況下都不能對(duì)我們本身代碼的功能有所影響,這樣的話(huà)剛才我們?cè)诖a中使用了一句assert(i++);是不妥的,因?yàn)槲覀円坏┙昧薬ssert,i++的語(yǔ)句就得不到執(zhí)行,對(duì)于接下來(lái)i值的使用就會(huì)出現(xiàn)問(wèn)題了,所以對(duì)于這樣的語(yǔ)句我們應(yīng)該是要分開(kāi)來(lái)實(shí)現(xiàn),寫(xiě)出如下兩句來(lái)替代, assert(i); i++;,所以這就對(duì)于斷言的使用有了相應(yīng)的要求,那么我們一般在什么情況下使用斷言呢?主要體現(xiàn)在一下幾個(gè)方面:

  1.可以在預(yù)計(jì)正常情況下程序不會(huì)到達(dá)的地方放置斷言。(如assert (0);)

  2.使用斷言測(cè)試方法執(zhí)行的前置條件和后置條件 。

  3.使用斷言檢查類(lèi)的不變狀態(tài),確保任何情況下,某個(gè)變量的狀態(tài)必須滿(mǎn)足。(如某個(gè)變量的變化范圍)

  對(duì)于上面的前置條件和后置條件可能有的讀者還不是很了解,那么看看下面的解釋你就明白了。

  前置條件斷言:代碼執(zhí)行之前必須具備的特性

  后置條件斷言:代碼執(zhí)行之后必須具備的特性

  前后不變斷言:代碼執(zhí)行前后不能變化的特性

  當(dāng)然在使用的斷言的過(guò)程中會(huì)有一些我們應(yīng)該注意的事項(xiàng)和養(yǎng)成一些良好的習(xí)慣,如:

  1.每個(gè)assert只檢驗(yàn)一個(gè)條件,因?yàn)橥瑫r(shí)檢驗(yàn)多個(gè)條件時(shí),如果斷言失敗,我們就無(wú)法直觀的判斷是哪個(gè)條件失敗

  2.不能使用改變環(huán)境的語(yǔ)句,就像我們上面的代碼改變了i變量,在實(shí)際編寫(xiě)代碼的過(guò)程中是不能這樣做的

  3.assert和后面的語(yǔ)句應(yīng)空一行,以形成邏輯和視覺(jué)上的一致感,也算是一種良好的編程習(xí)慣吧,讓編寫(xiě)的代碼有一種視覺(jué)上的美感

  4.有的地方,assert不能代替條件過(guò)濾

  5.放在函數(shù)參數(shù)的入口處檢查傳入?yún)?shù)的合法性

  6.斷言語(yǔ)句不可以有任何邊界效應(yīng)

  上面那么多的文字,似乎很枯燥,但是沒(méi)辦法,我們不能急功近利,還是要先堅(jiān)持看完文字描述部分,這樣在下面我們分析代碼的過(guò)程中就能很快知道為什么會(huì)出現(xiàn)那樣的問(wèn)題了,也能在自己編寫(xiě)代碼的時(shí)候熟練的使用assert,給自己的代碼調(diào)試帶來(lái)極大的便利,尤其是你在用C語(yǔ)言做工程項(xiàng)目的時(shí)候,如果你能夠在你的代碼中合理的使用assert,能使你創(chuàng)建更穩(wěn)定、質(zhì)量更好且不易于出錯(cuò)的代碼。當(dāng)需要在一個(gè)值為FALSE時(shí)中斷當(dāng)前操作的話(huà),可以使用斷言。單元測(cè)試必須使用斷言,除了類(lèi)型檢查和單元測(cè)試外,斷言還提供了一種確定各種特性是否在程序中得到維護(hù)的極好的方法。但凡優(yōu)秀的程序員都能夠在自己代碼中很好的使用assert,編寫(xiě)出高質(zhì)量的代碼來(lái)。

  說(shuō)了assert這么多的有點(diǎn),當(dāng)然也要說(shuō)說(shuō)它的缺點(diǎn)了。

  使用assert的缺點(diǎn)是,頻繁的調(diào)用會(huì)極大的影響程序的性能,增加額外的開(kāi)銷(xiāo)。所以在調(diào)試結(jié)束后,可以通過(guò)在包含#include 的語(yǔ)句之前插入 #define NDEBUG 來(lái)禁用assert調(diào)用。

  接下面分析一下下面的一段代碼:

  #include

  //#define NDEBUG

  #include

  int copy_string(char from[],char to[])

  {

  int i=0;

  while(to[i++]=from[i]);

  printf("%sn",to);

  return 1;

  }

  int main()

  {

  char str[]="this is a string!";

  char dec_str[206];

  printf("%sn",str);

  assert(copy_string(str,dec_str));

  printf("%sn",dec_str);

  return 0;

  }

  運(yùn)行結(jié)果為:

  

 

  在以上代碼的開(kāi)頭部分我們把#define NDEBUG給注釋掉了,所以我們啟用了assert,main函數(shù)中使用了assert(copy_string(str,dec_str));來(lái)實(shí)現(xiàn)copy_string函數(shù)的調(diào)用,在copy_string函數(shù)中我們使用了一句return 1,所以最終的函數(shù)調(diào)用結(jié)果就等價(jià)于是assert(1),所以接下來(lái)繼續(xù)執(zhí)行assert下面的打印語(yǔ)句,最終成功的打印了三條輸出語(yǔ)句,如果我們把開(kāi)頭的注釋部分打開(kāi),結(jié)果就只能成功的輸出起始部分一條打印語(yǔ)句。

  以上我們都是在圍繞著assert宏在講解,僅僅是教會(huì)大家如何來(lái)使用assert宏,那么接下來(lái)看看我們?nèi)绾蝸?lái)實(shí)現(xiàn)自己的斷言呢?

  接下來(lái)我們看看另外一段代碼:

  #include

  //#undef _EXAM_ASSERT_TEST_ //禁用

  #define _EXAM_ASSERT_TEST_ //啟用

  #ifdef _EXAM_ASSERT_TEST_ //啟用斷言測(cè)試

  void assert_report( const char * file_name, const char * function_name, unsigned int line_no )

  {

  printf( "n[EXAM]Error Report file_name: %s, function_name: %s, line %un",

  file_name, function_name, line_no );

  }

  #define ASSERT_REPORT( condition )

  do{

  if ( condition )

  NULL;

  else

  assert_report( __FILE__, __func__, __LINE__ );

  }while(0)

  #else // 禁用斷言測(cè)試

  #define ASSERT_REPORT( condition ) NULL

  #endif /* end of ASSERT */

  int main( void )

  {

  int i;

  i=0;

  // assert(i++);

  ASSERT_REPORT(i);

  printf("%dn",i);

  return 0;

  }

  運(yùn)行結(jié)果如下:

  [EXAM]Error Report file_name: assert3.c, function_name: main, line 29

  0

  細(xì)心的讀者會(huì)發(fā)現(xiàn)我們并沒(méi)有使用斷言來(lái)結(jié)束當(dāng)前程序的執(zhí)行,所以在斷言下面的printf成功的打印出了i的當(dāng)前值,當(dāng)然我們也可以做適當(dāng)?shù)男薷?,在斷言出發(fā)現(xiàn)錯(cuò)誤,那么就調(diào)用 abort();來(lái)使當(dāng)前正在執(zhí)行的程序異常終止,修改如下:

  #include

  #include

  //#undef _EXAM_ASSERT_TEST_ //禁用

  #define _EXAM_ASSERT_TEST_ //啟用

  #ifdef _EXAM_ASSERT_TEST_ //啟用斷言測(cè)試

  void assert_report( const char * file_name, const char * function_name, unsigned int line_no )

  {

  printf( "n[EXAM]Error Report file_name: %s, function_name: %s, line %un",

  file_name, function_name, line_no );

  abort();

  }

  #define ASSERT_REPORT( condition )

  do{

  if ( condition )

  NULL;

  else

  assert_report( __FILE__, __func__, __LINE__ );

  }while(0)

  #else // 禁用斷言測(cè)試

  #define ASSERT_REPORT( condition ) NULL

  #endif /* end of ASSERT */

  int main( void )

  {

  int i;

  i=0;

  // assert(i++);

  ASSERT_REPORT(i);

  printf("%dn",i);

  return 0;

  }

  運(yùn)行結(jié)果如下:

  [EXAM]Error Report file_name: assert3.c, function_name: main, line 31

  Aborted

  此時(shí)就不會(huì)在執(zhí)行接下來(lái)的打印語(yǔ)句了。看看我們自己的實(shí)現(xiàn)方式就知道,我們自己編寫(xiě)的斷言可以比直接調(diào)用assert宏可以得到更多的信息量,主要是由于我們自己編寫(xiě)的斷言更加的具有靈活性,可以根據(jù)自己的需要來(lái)打印輸出不同的信息,同時(shí)也可以對(duì)于不同類(lèi)型的錯(cuò)誤或者警告信息使用不同的斷言,這也是在工程代碼中經(jīng)常使用的做法。如果你在關(guān)注代碼運(yùn)行結(jié)果的同時(shí)也認(rèn)真的閱讀了我的代碼,你會(huì)發(fā)現(xiàn)其中我在宏定義中使用了一個(gè)do{}while(0),使用它有什么好處呢,或許在以上的代碼中并沒(méi)有體現(xiàn)出來(lái),那么我們看看下面的代碼你就知道了。

  #include

  void print_1(void)

  {

  printf("print_1n");

  }

  void print_2(void)

  {

  printf("print_2n");

  }

  #define printf_value()

  print_1();

  print_2();

  int main( void )

  {

  int i=0;

  if(i==1)

  printf_value();

  return 0;

  }

  運(yùn)行結(jié)果:

  

 

  還是備份一下文章描述,以防圖片打開(kāi)失敗給讀者帶來(lái)困擾。

  print_2

  Press any key to continue

  看了上面運(yùn)行結(jié)果可能有的讀者會(huì)很疑惑為什么會(huì)出現(xiàn)以上的錯(cuò)誤呢?!if語(yǔ)句的條件不滿(mǎn)足,那么print_value()函數(shù)應(yīng)該不會(huì)被調(diào)用啊,怎么會(huì)打印呢。如果我們把上面的printf_value()替換為 print_1(); print_2();,就會(huì)很清楚的發(fā)現(xiàn)if語(yǔ)句在此的作用僅僅是不調(diào)用print_1();,而print_2();在控制之外,所以出現(xiàn)了上面的結(jié)果,有的讀者可能會(huì)馬上想到我們加上一個(gè){}不就好了嗎,在這里的確是加一個(gè){}就可以了,因?yàn)檫@里是一個(gè)特殊情況,沒(méi)有else語(yǔ)句,如果我們?cè)谝陨系暮甓x中使用{},加入else語(yǔ)句后再來(lái)看看代碼。

  #include

  void print_1(void)

  {

  printf("print_1n");

  }

  void print_2(void)

  {

  printf("print_2n");

  }

  #define printf_value()

  {

  print_1();

  print_2();}

  int main( void )

  {

  int i=0;

  if(i==1)

  printf_value();

  else

  printf("add else word!!!");

  return 0;

  }

  看似正確的代碼,我們編譯就會(huì)出現(xiàn)如下錯(cuò)誤:

  error C2181: illegal else without matching if

  為什么會(huì)出現(xiàn)這樣的錯(cuò)誤呢?因?yàn)槲覀兙帉?xiě)C語(yǔ)言代碼時(shí),在每個(gè)語(yǔ)句后面加分號(hào)是一種約定俗成的習(xí)慣,以上代碼中我們?cè)趐rintf_value()語(yǔ)句后面加了一個(gè)分號(hào),正是由于這個(gè)分號(hào)的作用使得else沒(méi)有與之相對(duì)應(yīng)的if,所以編譯出錯(cuò)。但是如果我們使用do{}while(0)就不會(huì)出現(xiàn)這些問(wèn)題,所以我們?cè)诰帉?xiě)代碼的時(shí)候應(yīng)該學(xué)會(huì)在宏定義中使用do{}while(0)。

  C語(yǔ)言斷言?xún)?nèi)容的講解到此就該結(jié)束了,上面內(nèi)容已給出了在C語(yǔ)言編寫(xiě)代碼的過(guò)程中斷言較為詳細(xì)的使用,其中后面使用我們自己實(shí)現(xiàn)的斷言算得上是一個(gè)比較經(jīng)典的斷言設(shè)計(jì)方法了,讀者可以在自己以后編寫(xiě)C語(yǔ)言代碼的過(guò)程中參考下。由于本人水平有限,博客中的不妥或錯(cuò)誤之處在所難免,殷切希望讀者批評(píng)指正。同時(shí)也歡迎讀者共同探討相關(guān)的內(nèi)容,如果樂(lè)意交流的話(huà)請(qǐng)留下你寶貴的意見(jiàn)。

c語(yǔ)言相關(guān)文章:c語(yǔ)言教程




關(guān)鍵詞: C語(yǔ)言 斷言

評(píng)論


相關(guān)推薦

技術(shù)專(zhuān)區(qū)

關(guān)閉