统计C/C++代码行数

时间:2023-03-08 16:05:58
统计C/C++代码行数

近日在写一个统计项目中C/C++文件(后缀名:C/CPP/CC/H/HPP文件)代码行数的小程序。给定包含C/C++代码的目录,统计目录里所有C/C++文件的总代码行数、有效代码行数、注释行数、空白行数。

其中:总代码行数 =(有效代码行数+注释行数+空白行数)

每找到一个目标代码文件,就创建任务投进线程池。线程池的设计基于任务,基于任务相比基于线程的优势,请参考Scott Meyers撰写的Moderm Effective C++一书。

先给出程序运行的效果,见下图:

统计C/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 /* 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行。

对于这么个复杂代码段,市面上某号称源码统计专家的工具给出的结果如下:

统计C/C++代码行数

可以看到,在面对注释风格多变且复杂的代码文件时,专家工具变“砖”家,结果毫不可信。

而本程序输出结果,完全正确。

统计C/C++代码行数

上述复杂代码段中,值得关注的是原始字符串的处理,其语法为R"()",小括号内放原始字符串的内容

 std::string comment_test = R"({
// comment /* with nested comment */
"a": ,
// comment
// continued
"b": "text",
/* multi
line
comment
// line-comment-inside-multiline-comment
*/
// and single-line comment
// and single-line comment /* multiline inside single line */
"c": [, , ]
// and single-line comment at end of object
})";

conmment_tes存放的内容是

统计C/C++代码行数

就是说里面的// 和/* */注释符号不能被认定为注释符号,哪怕它们在其中混用,注释嵌套着注释,不管是多复杂的组合,也是原始字符串的一部分,是有效代码。程序的目的就是利用原始字符串的语法构造规则,找出原始字符串的起始和结束位置。

在识别过程中,本程序做的工作其实是编译期语法/词法分析的一部分功能,这也让我更加了解原始字符串和嵌套注释的语法规则。特别地,发现了vs注释一个有意思的地方。vs注释的快捷键会优先选择/**/风格注释,在/**/不能胜任的地方,会使用//代替。

以前看过一篇文章,建议注释只用//,而不要用/* */,更不要用嵌套的/* */,因为后两种注释加大了文本分析程序解析的难度。 如果注释用//开头,很容易就识别该行代码就是注释了。我在做这个代码行统计小程序时,对这条建议有了更深的体会。用//代替/**/这个建议,应该作为代码规范执行下去。

写程序难免踩坑,在这里记下来,避免后面踩到同样的坑。

1. 在控制台程序输入文件夹路径时,路径如果包含空格,cmd会把空格前后内容当作不同的参数,程序处理参数会错误(路径无效)。把路径用双引号包含起来后就正常了。

统计C/C++代码行数

2.程序在控制台运行出现中文乱码,工程设置是unicode,程序打印的文件路径含中文,而控制台默认代码页是936,GBK,开发环境和输出环境编码不一致,所以会乱码。

乱码截图一张,图中的问号其实是中文。

统计C/C++代码行数

为适应控制台的编码,使用setlocale(LC_CTYPE, "")就好了;

3.标准库里的ofstream的<<重载符,对宽字符的处理不好,比如写入std::wstring内容会出错,而使用std::string则不会有问题。但代码路径有中文的情况下,无法输出。解决办法就是把ofstream换成wofstream。同时调用file.imbue(locale("", locale::all ^ locale::numeric));设置中文输出环境。

4.标准库的向量容器push_back(插入元素)非线程安全,在多线程环境下往此容器里写东西时需加锁。当然标准库里的大多数容器操作都是非线程安全的,使用时需谨慎。