統計C/C++代碼行數


近日在寫一個統計項目中C/C++文件(后綴名:C/CPP/CC/H/HPP文件)代碼行數的小程序。給定包含C/C++代碼的目錄,統計目錄里所有C/C++文件的總代碼行數、有效代碼行數、注釋行數、空白行數。

其中:總代碼行數 =(有效代碼行數+注釋行數+空白行數)

每找到一個目標代碼文件,就創建任務投進線程池。線程池的設計基於任務,基於任務相比基於線程的優勢,請參考Scott Meyers撰寫的Moderm Effective C++一書。

先給出程序運行的效果,見下圖:

近5萬的代碼文件,1183萬總代碼行數,不到5分鍾統計完成,速度還是很快的。有人問了,用5分鍾才統計完,怎么也不能說快吧。咱不耍嘴皮子,用結果說話。作為對比的上面那款源碼統計專家工具,在對同一個目錄里的文件進行分析統計時,半個小時過去還沒給出結果。這工具單文件分析都不准,面對5萬個文件,結果只怕會錯的離譜,運行又慢,我想也沒等下去的必要了,果斷給×掉了。

需要程序的,可以在文末評論留下郵箱O(∩_∩)O

對代碼文件的分析中,空白行好處理,關鍵在於識別有效代碼和注釋代碼。大的識別原則有:

1.注釋符合文法,不能讓編譯期報錯。這是最重要的原則。

2.注釋中的空行是空行,原始字符串中的空行不是空行

3.原始字符串里的注釋(行注釋/塊注釋)是有效代碼,不能當作注釋統計

4.注釋中的原始字符串是注釋,不能當作有效代碼統計

為了驗證程序邏輯的嚴密性和准確性,設計了如下的復雜的代碼片段:

 1 // idxRawStringStart = curLine.find(R"(R"()");
 2 string comment_test = R"({
 3       // comment /* with nested comment */
 4       // comment
 5       "a": "text",
 6       /* multi
 7         // line-comment-inside-multiline-comment
 8       */
 9       
10     })";
11 
12 idxRawStringStart = curLine.find(R"(R"()");
13 idxRawStringStart = curLine.find(R"(R"(\
14 )");
15 /**************************************************************************
16 *    功能描述:
17 **************************************************************************/
18 /*
19 
20          * Since some memmove()'s erroneously fail to handle
21          * overlapping regions, we'll do the shift by hand.
22          */
23 int a = /*b*/ 0;
24 int b = 0 // comment
25 /*a = b*/
26 // int a = b;
27  }
28  /////////////////////////////////////////////////////////////////////////
29 }

 

這29行代碼中,從程序員的角度看,綠色部分是注釋共11行,紅色部分是代碼共16行,line 9是空行但因為包含在原始字符串中,所以是有效代碼行。

真正的空行只有 line 11和19,共2行。

對於這么個復雜代碼段,市面上某號稱源碼統計專家的工具給出的結果如下:

 

可以看到,在面對注釋風格多變且復雜的代碼文件時,專家工具變“磚”家,結果毫不可信。

而本程序輸出結果,完全正確。

 

 上述復雜代碼段中,值得關注的是原始字符串的處理,其語法為R"()",小括號內放原始字符串的內容

 1 std::string comment_test = R"({
 2       // comment /* with nested comment */
 3       "a": 1,
 4       // comment
 5       // continued
 6       "b": "text",
 7       /* multi
 8          line
 9          comment
10         // line-comment-inside-multiline-comment
11       */
12       // and single-line comment
13       // and single-line comment /* multiline inside single line */
14       "c": [1, 2, 3]
15       // and single-line comment at end of object
16     })";

conmment_tes存放的內容是

就是說里面的// 和/* */注釋符號不能被認定為注釋符號,哪怕它們在其中混用,注釋嵌套着注釋,不管是多復雜的組合,也是原始字符串的一部分,是有效代碼。程序的目的就是利用原始字符串的語法構造規則,找出原始字符串的起始和結束位置。

在識別過程中,本程序做的工作其實是編譯期語法/詞法分析的一部分功能,這也讓我更加了解原始字符串和嵌套注釋的語法規則。特別地,發現了vs注釋一個有意思的地方。vs注釋的快捷鍵會優先選擇/**/風格注釋,在/**/不能勝任的地方,會使用//代替。

以前看過一篇文章,建議注釋只用//,而不要用/* */,更不要用嵌套的/* */,因為后兩種注釋加大了文本分析程序解析的難度。 如果注釋用//開頭,很容易就識別該行代碼就是注釋了。我在做這個代碼行統計小程序時,對這條建議有了更深的體會。用//代替/**/這個建議,應該作為代碼規范執行下去。

 寫程序難免踩坑,在這里記下來,避免后面踩到同樣的坑。

1. 在控制台程序輸入文件夾路徑時,路徑如果包含空格,cmd會把空格前后內容當作不同的參數,程序處理參數會錯誤(路徑無效)。把路徑用雙引號包含起來后就正常了。

2.程序在控制台運行出現中文亂碼,工程設置是unicode,程序打印的文件路徑含中文,而控制台默認代碼頁是936,GBK,開發環境和輸出環境編碼不一致,所以會亂碼。

亂碼截圖一張,圖中的問號其實是中文。

為適應控制台的編碼,使用setlocale(LC_CTYPE, "")就好了;

3.標准庫里的ofstream的<<重載符,對寬字符的處理不好,比如寫入std::wstring內容會出錯,而使用std::string則不會有問題。但代碼路徑有中文的情況下,無法輸出。解決辦法就是把ofstream換成wofstream。同時調用file.imbue(locale("", locale::all ^ locale::numeric));設置中文輸出環境。

4.標准庫的向量容器push_back(插入元素)非線程安全,在多線程環境下往此容器里寫東西時需加鎖。當然標准庫里的大多數容器操作都是非線程安全的,使用時需謹慎。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM