当前位置: 首页 > news >正文

尝试把clang-tidy集成到AWTK项目

前言

项目经过一段时间的耕耘终于进入了团队开发阶段,期间出现了很多问题,其中一个就是开会讨论团队的代码风格规范,目前项目代码风格比较混乱,有的模块是驼峰,有的模块是匈牙利,后面经过讨论,决定采用匈牙利,和awtk库的api风格一致。

讨论完之后就是改代码了,有十几个模块几百个函数要改,一个个人工去改显然费时费力,改的时候就在想这种东西有没有自动化的做法了?

于是下班开始探索一番,首先是尝试用AI写一个批量扫描文件,用正则匹配不符合规则的python脚本,结果费时费力,效果明显不好。

不对,c/c++发展了十几年,这类问题难道没有现成的方案?

后面搜了点原理,正则的思路对于这类问题显然不对,没法识别函数和变量,那只能用抽象语法树了。对象是c/c++,应该是一个类似c/c++编译器的静态分析工具。

最后找到了clang-tidy。

尝试集成1.0

首先下载clang-tidy:

pip install clang-tidy

假设示例项目src结构如下:

src
├── application.c
├── common
│   ├── navigator.c
│   ├── navigator.h
│   └── temperature.h
├── main.c
├── modules
│   ├── libframebuffer.c
│   └── libframebuffer.h
├── pages
│   └── home_page.c
└── SConscript

要修改的文件例:libframebuffer,里面api是驼峰格式,需要自动改成匈牙利格式,home_page有引用。

libframebuffer.h

#ifndef LIBFRAMEBUFFER_H
#define LIBFRAMEBUFFER_H
typedef struct {int width;int height;int bpp;void* buffer;
} LibFrameBuffer;LibFrameBuffer* LibFrameBufferInit();void LibFrameBufferFree(LibFrameBuffer* fb);#endif 	

libframebuffer.c

#include "libframebuffer.h"
#include <stdlib.h>LibFrameBuffer* LibFrameBufferInit()
{return (LibFrameBuffer*)malloc(sizeof(LibFrameBuffer));
}void LibFrameBufferFree(LibFrameBuffer* fb)
{free(fb);
}

home_page.c

#include "awtk.h"
#include "libframebuffer.h"/*** 初始化窗口的子控件*/
static ret_t visit_init_child(void* ctx, const void* iter) {widget_t* win = WIDGET(ctx);widget_t* widget = WIDGET(iter);const char* name = widget->name;// 初始化指定名称的控件(设置属性或注册事件),请保证控件名称在窗口上唯一if (name != NULL && *name != '\0') {}return RET_OK;
}/*** 初始化窗口*/
ret_t home_page_init(widget_t* win, void* ctx) {(void)ctx;return_value_if_fail(win != NULL, RET_BAD_PARAMS);widget_foreach(win, visit_init_child, win);LibFrameBufferInit();return RET_OK;
}

在项目根目录下设置好.clang-tidy,这个是clang-tidy的配置文件, clang-tidy有很多的check项,和代码命名风格相关的是readability-identifier-naming,这个checker下面有非常多类型的拼写设置,我这里设置了类, 变量,函数,宏四个类型的拼写项,其中前三个的lower_case对应的就是匈牙利小写写法,最后一个UPPER_CASE是全大写写法。

readability-identifier-naming的具体设置可见:https://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-naming.html

Checks: >readability-identifier-naming
CheckOptions:- key:             readability-identifier-naming.ClassCasevalue:           lower_case- key:             readability-identifier-naming.VariableCasevalue:           lower_case- key:             readability-identifier-naming.FunctionCasevalue:           lower_case- key:             readability-identifier-naming.MacroDefinitionCasevalue:           UPPER_CASE

clang-tidy本身相当于半个编译器,会对翻译单元的头文件进行处理,而不是像脚本那样单纯进行文本解析,所以在给clang-tidy一个找不到头文件的源文件时会导致报错:

zhangdalin@huwyi-ubuntu:~/AWStudioProjects/awtk_clang_tidy_test$ clang-tidy src/pages/home_page.
Error while trying to load a compilation database:
Could not auto-detect compilation database for file "src/pages/home_page."
No compilation database found in /home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages or any parent directory
fixed-compilation-database: Error while opening fixed database: No such file or directory
json-compilation-database: Error while opening JSON database: No such file or directory
Running without flags.
Error while processing /home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/home_page..
error: no input files [clang-diagnostic-error]
error: no such file or directory: '/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/home_page.' [clang-diagnostic-error]
error: unable to handle compilation, expected exactly one compiler job in '' [clang-diagnostic-error]
Found compiler error(s).

因此必须找到一个方法让clang-tidy能够读取到项目所有的源文件和头文件。

使用compiler_command.json

compiler_command.json上面记录了项目每个编译的编译命令,输入源文件和输出obj,类似于给工具一个符号表,clang-tidy通过这张符号表进行解析。

在CMake可以设置set(CMAKE_EXPORT_COMPILE_COMMANDS ON)生成compiler_command.json给clang-tidy,我于是去看scons有没有对应设置,还好有,见:https://scons.org/doc/latest/HTML/scons-user/ch27.html

在SConstruct和SConscript分别添加env.Tool('compilation_db')env.CompilationDatabase()[0], scons就能在BIN_DIR得到compiler_command.json,我试过在SConscript把两个指令一起加进去,不知道为什么在windows会报找不到AttributeError: 'SConsEnvironment' object has no attribute '__COMPILATIONDB_Entry':的错误,Linux上就正常。我这里的情形是跨平台,windows和linux都需要考虑使用。

SConstruct

+env = helper.call(DefaultEnvironment)
+env.Tool('compilation_db')

SConscript

sources = Glob('**/*.c') + Glob('*.c')
+compile_database = env.CompilationDatabase()[0]

scons编译,在BIN_DIR得到compiler_command.json,验证通过。

接下来写clang-tidy调用逻辑,scons本身是python脚本,直接封装成函数就好了,难点在于如何让scons执行时,如果需要将封装函数也一并调用,对此,scons提供了Options选项, 可以自定义选项来控制调用逻辑,见:https://scons.org/doc/latest/HTML/scons-user/ch10.html

这里就提供一个--clang-tidy选项给scons,修改后的SConscript:

import os
import subprocess
from SCons.Script import *env = DefaultEnvironment().Clone()
BIN_DIR = os.environ['BIN_DIR']
LIB_DIR = os.environ['LIB_DIR']sources = Glob('**/*.c') + Glob('*.c')AddOption('--clang-tidy',dest='clang_tidy',metavar='BOOL',action='store_true',default=False,help="Don't run clang-tidy static code analysis automatically")
compile_database = env.CompilationDatabase()[0]
program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])def run_clang_tidy(target, source, env):source_file = str(source[0])compilation_db = str(source[1])cmd = f"clang-tidy -p {compilation_db} -header-filter=.* {source_file}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0print(f"clang_tidy option = {GetOption('clang_tidy')}")if GetOption('clang_tidy') == True:for source in sources:run_clang_tidy('',[source, compile_database], env)

执行,会看到clang-tidy把三方库的头文件也加进了检测,原因是设置用了-header-filter=.*来将项目头文件加入检测,不加这个选项是只能检测c文件的,而且函数只能检测到没有加入头文件声明的函数。

/home/zhangdalin/AWStudioProjects/awtk/3rd/SDL/include/SDL_stdinc.h:569:9: warning: invalid case style for macro definition 'SDL_malloc' [readability-identifier-naming]
#define SDL_malloc malloc^~~~~~~~~~SDL_MALLOC
/home/zhangdalin/AWStudioProjects/awtk/3rd/SDL/include/SDL_stdinc.h:570:9: warning: invalid case style for macro definition 'SDL_calloc' [readability-identifier-naming]
#define SDL_calloc calloc^~~~~~~~~~SDL_CALLOC
/home/zhangdalin/AWStudioProjects/awtk/3rd/SDL/include/SDL_stdinc.h:571:9: warning: invalid case style for macro definition 'SDL_realloc' [readability-identifier-naming]
#define SDL_realloc realloc^~~~~~~~~~~SDL_REALLOC

解决方法是在.clang-tidy下设置HeaderFilterRegex, 原理是clang-tidy搜索的文件是绝对路径的列表,设置HeaderFilterRegex将只检测和设置的正则匹配的文件路径。

注意原来脚本的-header-filter=.*要去掉,否则会覆盖.clang-tidy上的设置,那就失效了。

.clang-tidy

HeaderFilterRegex: 'awtk_clang_tidy_test\\src\\.*|awtk_clang_tidy_test/src/.*' #左边匹配windows下项目路径,右边匹配linux下项目路径

SConscript

def run_clang_tidy(target, source, env):source_file = str(source[0])compilation_db = str(source[1])cmd = f"clang-tidy -p {compilation_db} {source_file}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0

自此一个基础的集成就完成了。

Running: clang-tidy -p compile_commands.json common/navigator.c
2024 warnings generated.
Suppressed 2024 warnings (2024 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running: clang-tidy -p compile_commands.json modules/libframebuffer.c
679 warnings generated.
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:4:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit()^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:9:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb)^~~~~~~~~~~~~~~~~~lib_frame_buffer_free

尝试集成2.0

看似简单,实际引用到项目还是有问题,scons不像CMake, 编译compiler_command.json的过程和编译项目的过程是绑死的,一定要编译完项目才会生成compiler_command.json,我曾经试过把env.Program只生成json,发现json能够出来,但是里面不带任何项目相关的文件编译命令,只有编译整个项目才能更新compiler_command.json

这就是scons的不便之处了,项目是没法先静态检测再编译的,只能放到编译后再检测了。如果是CMake,大可以通过cmake -S. -B buildcmake --build build把构建compiler_command.json和编译项目的过程按先后分开。

怎么让scons在env.Program后执行自定义命令?可以用env.Command, scons编译先后顺序涉及到builder的概念,这里就不深入了,参考:

https://stackoverflow.com/questions/36273482/scons-strange-execution-order

https://scons.org/doc/1.3.0/HTML/scons-user/c3721.html

修改后的SConscript如下:

import os
import subprocess
from SCons.Script import *env = DefaultEnvironment().Clone()
BIN_DIR = os.environ['BIN_DIR']
LIB_DIR = os.environ['LIB_DIR']sources = Glob('**/*.c') + Glob('*.c')AddOption('--clang-tidy',dest='clang_tidy',metavar='BOOL',action='store_true',default=False,help="Don't run clang-tidy static code analysis automatically")
compile_database = env.CompilationDatabase()[0]
program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])def run_clang_tidy(target, source, env):source_file = str(source[0])compilation_db = str(source[1])cmd = f"clang-tidy -p {compilation_db}  {source_file}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0print(f"clang_tidy option = {GetOption('clang_tidy')}")if GetOption('clang_tidy') == True:for source in sources:# 会在env.Program之后执行env.Command(target=f"{os.path.basename(str(source))}.clang-tidy.log",source=[source, compile_database],action=Action(run_clang_tidy, cmdstr="Running clang-tidy on ${SOURCE}"))

把编译出的compile_commands.json删了,然后scons --clang-tidy测试,正常运行:

zhangdalin@huwyi-ubuntu:~/AWStudioProjects/awtk_clang_tidy_test$ scons --clang-tidy
scons: Reading SConscript files ...
APP_SCRIPTS_ROOT:/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/scripts
AWTK_ROOT: /home/zhangdalin/AWStudioProjects/awtk
AWTK_SCRIPTS_ROOT: /home/zhangdalin/AWStudioProjects/awtk/scripts
...
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/bin exist.
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/lib exist.
False
AWTK_ROOT: /home/zhangdalin/AWStudioProjects/awtk
TKC_ONLY: False
{}
/home/zhangdalin/AWStudioProjects/awtk/bin/libawtk.so==>/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/bin
clang_tidy option = True
scons: done reading SConscript files.
scons: Building targets ...
Building compilation database src/compile_commands.json
Running clang-tidy on src/application.c
Running: clang-tidy -p src/compile_commands.json  src/application.c
2024 warnings generated.
Suppressed 2024 warnings (2024 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running clang-tidy on src/pages/home_page.c
Running: clang-tidy -p src/compile_commands.json  src/pages/home_page.c
2027 warnings generated.
src/pages/home_page.c:8:13: warning: unused variable 'win' [clang-diagnostic-unused-variable]widget_t* win = WIDGET(ctx);^
Suppressed 2026 warnings (2026 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running clang-tidy on src/modules/libframebuffer.c
Running: clang-tidy -p src/compile_commands.json  src/modules/libframebuffer.c
679 warnings generated.
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:4:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit()^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:9:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb)^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
Suppressed 677 warnings (677 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running clang-tidy on src/main.c
Running: clang-tidy -p src/compile_commands.json  src/main.c
2028 warnings generated.
Suppressed 2028 warnings (2028 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
Running clang-tidy on src/common/navigator.c
Running: clang-tidy -p src/compile_commands.json  src/common/navigator.c
2024 warnings generated.
Suppressed 2024 warnings (2024 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
scons: done building targets.

能正常运行了,这时候就可以加--fix选项去自动改naming错误了,我具体测试了下,还是没法全部一次性修改的,有些引用函数的文件就没有改到,在这个例子是只改了libframebuffer.c,libframebuffer.h没有修改。好在数量少,自动化工具能解决大部分。

然而还是有问题,这个方案只能在linux下使用,在windows上会报unable to handle compilation错误:

Running clang-tidy on src\modules\libframebuffer.c
Running: clang-tidy --fix -p src\compile_commands.json  src\modules\libframebuffer.c
Error while processing D:\AWStudioProjects\awtk_clang_tidy_test\src\modules\libframebuffer.c.
error: unable to handle compilation, expected exactly one compiler job in '' [clang-diagnostic-error]
warning: @C:\Users\z5843\AppData\Local\Temp\tmp_8x5etvp.lnk: 'linker' input unused [clang-diagnostic-unused-command-line-argument]
Found compiler errors, but -fix-errors was not specified.
Fixes have NOT been applied.

原因是我awtk在windows上设置的用msvc编译,scons对于msvc生成的compiler_command.json里面调用指令实际上要通过一层快捷方式文件去中转,称之为响应文件(response files), 因为windows的命令行有长度限制,没法一次调用太长的命令。

[{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpwbrtpkke.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\common\\navigator.c","output": "src\\common\\navigator.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpmf4y8gp4.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\modules\\libframebuffer.c","output": "src\\modules\\libframebuffer.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpjye3ldpl.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\pages\\home_page.c","output": "src\\pages\\home_page.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpjtyr5i36.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\application.c","output": "src\\application.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpn639b_bf.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "src\\main.c","output": "src\\main.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpghyoi9f_.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "D:\\AWStudioProjects\\awtk\\3rd\\gtest\\googletest\\src\\gtest-all.cc","output": "D:\\AWStudioProjects\\awtk\\3rd\\gtest\\googletest\\src\\gtest-all.obj"},{"command": "cl @C:\\Users\\z5843\\AppData\\Local\\Temp\\tmpubp7ar8o.lnk","directory": "D:\\AWStudioProjects\\awtk_clang_tidy_test","file": "tests\\main.cc","output": "tests\\main.obj"}
]

这些快捷方式第一次编译才会创建,在第二次编译时就能复用,而且如果修改了源文件(比如修改代码或者开关宏),这些快捷方式就失效了,这就是为什么我改源文件或者删除compiler_command.json都会导致报unable to handle compilation错误。我在网上找了一圈都不知道怎么关掉这个响应文件。

其实也可以设置awtk编译方式为MINGW, 这样就没有问题了,可惜我实际项目一些三方库是msvc编译的,和mingw不兼容,这个选择就废了。

没法泛化到windows,方案还要继续改进。

BTW: CMake+MSVC在windows上是不会生成compiler_command.json的,不知道scons怎么做到的生成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

尝试集成3.0

其实不使用compiler_command.json, 也可以通过给clang-tidy直接指明源文件和头文件路径的方式来绕过,这样检测就不依赖于编译了。

可以通过直接用scons的配置文件,比较方便,也可以独立脚本,不过路径需要脚本去暴力匹配,如果项目庞大,写起匹配逻辑会比较麻烦,这里选用前者。

之前的脚本其实还有个问题,run_clang_tidy执行一次只检测一个文件,一个文件就执行一次clang-tidy命令,这样使用是低效的,实际上clang-tidy可以批量集成多个文件一起去分析。

run_clang_tidy修改,拆除来放到clang_tidy_helper.py,置于scripts文件夹:

import subprocessdef run_clang_tidy(flags, source_file_str, cxxflags, include_file_str):cmd = f"clang-tidy {flags} {source_file_str} -- {cxxflags} {include_file_str}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0

修改后的SConscript如下:

import os
import subprocess
from scripts.clang_tidy_helper import run_clang_tidy
from SCons.Script import *env = DefaultEnvironment().Clone()
BIN_DIR = os.environ['BIN_DIR']
LIB_DIR = os.environ['LIB_DIR']sources = Glob('**/*.c') + Glob('*.c')AddOption('--clang-tidy',dest='clang_tidy',metavar='BOOL',action='store_true',default=False,help="Don't run clang-tidy static code analysis automatically")program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])if GetOption('clang_tidy') == True:sources_file_str = ' '.join(os.path.normpath(str(s)) for s in sources)    flags = env['CCFLAGS'] include_file_str = ' '.join([f'-I{os.path.normpath(str(i))}' for i in env['CPPPATH']])run_clang_tidy('', sources_file_str, flags, include_file_str)

运行,可以看到输出不一样了,会统计文件数,累计warning和error数,能检测出libframebuffer的naming错误,不过这次头文件和源文件的error都可以显示了,加入–fix选项运行,发现头文件,源文件,引用的文件都被修正了。

/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:4:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit()^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:4:17: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:9:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb)^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.c:9:6: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.h:10:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit();^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.h:10:17: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.h:12:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb);^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/modules/libframebuffer.h:12:6: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/../modules/libframebuffer.h:10:17: warning: invalid case style for function 'LibFrameBufferInit' [readability-identifier-naming]
LibFrameBuffer* LibFrameBufferInit();^~~~~~~~~~~~~~~~~~lib_frame_buffer_init
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/../modules/libframebuffer.h:10:17: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/home_page.c:28:3: note: FIX-IT applied suggested code changesLibFrameBufferInit();^
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/../modules/libframebuffer.h:12:6: warning: invalid case style for function 'LibFrameBufferFree' [readability-identifier-naming]
void LibFrameBufferFree(LibFrameBuffer* fb);^~~~~~~~~~~~~~~~~~lib_frame_buffer_free
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/../modules/libframebuffer.h:12:6: note: FIX-IT applied suggested code changes
/home/zhangdalin/AWStudioProjects/awtk_clang_tidy_test/src/pages/home_page.c:8:13: warning: unused variable 'win' [clang-diagnostic-unused-variable]widget_t* win = WIDGET(ctx);^
clang-tidy applied 7 of 7 suggested fixes.
Suppressed 8802 warnings (8802 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.
scons: done reading SConscript files.
scons: Building targets ...
scons: `.' is up to date.
scons: done building targets.

如果不想编译项目,单独做静态检测,也比较简单:

AddOption('--no-compile',dest='no_compile',metavar='BOOL',action='store_true',default=False,help="Don't compile the code")if GetOption('no_compile') == False:program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])

从这边开始,总算就没有什么比较难受的使用问题了,而且可以集成检测工具一起编译,也可以单独检测。

还有很多细节,比如改用run-clang-tidy.py脚本提速,ci/cd集成,时间原因先写到这里,后面有时间看看如何实现。

最终的脚本:

SConscript

import os
import subprocess
from scripts.clang_tidy_helper import run_clang_tidy
from SCons.Script import *env = DefaultEnvironment().Clone()
BIN_DIR = os.environ['BIN_DIR']
LIB_DIR = os.environ['LIB_DIR']sources = Glob('**/*.c') + Glob('*.c')AddOption('--clang-tidy',dest='clang_tidy',metavar='BOOL',action='store_true',default=False,help="Don't run clang-tidy static code analysis automatically")AddOption('--no-compile',dest='no_compile',metavar='BOOL',action='store_true',default=False,help="Don't compile the code")if GetOption('no_compile') == False:program = env.Program(os.path.join(BIN_DIR, 'demo'), sources, LIBS = env['LIBS'])if GetOption('clang_tidy') == True:sources_file_str = ' '.join(os.path.normpath(str(s.get_abspath())) for s in sources)    flags = env['CCFLAGS'] include_file_str = ' '.join([f'-I{os.path.normpath(str(i))}' for i in env['CPPPATH']])run_clang_tidy('--fix ', sources_file_str, flags, include_file_str)

SConstruct

import os
import scripts.app_helper as app
import clang_tidy as tidyCUSTOM_WIDGET_LIBS = [{"root" : '3rd/awtk-widget-label-rotate','shared_libs': ['label_rotate'],'static_libs': []
}]DEPENDS_LIBS = CUSTOM_WIDGET_LIBS + []helper = app.Helper(ARGUMENTS)
helper.set_deps(DEPENDS_LIBS)# app.prepare_depends_libs(ARGUMENTS, helper, DEPENDS_LIBS)
env = helper.call(DefaultEnvironment)SConscriptFiles = ['src/SConscript', 'tests/SConscript']
helper.SConscript(SConscriptFiles)

scripts/clang_tidy_helper.py

import os
import sys
import platform
import re
import subprocessdef run_clang_tidy(flags, source_file_str, cxxflags, include_file_str):cmd = f"clang-tidy {flags} {source_file_str} -- {cxxflags} {include_file_str}"print(f"Running: {cmd}")subprocess.run(cmd, shell=True)return 0

补充

clang-tidy不一定能一次解决所有的代码规范问题,比如私有函数习惯写__前缀,即使是用匈牙利写法也会被误报:

D:\AWStudioProjects\awtk_clang_tidy_test\src\modules\lib_framebuffer.c:11:6: warning: invalid case style for function '__lib_framebuffer_load_format' [readability-identifier-naming]      11 | void __lib_framebuffer_load_format(lib_framebuffer *fb){|      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~|      lib_framebuffer_load_format
D:\AWStudioProjects\awtk_clang_tidy_test\src\modules\lib_framebuffer.c:31:6: warning: invalid case style for function '__lib_framebuffer_init' [readability-identifier-naming]31 | void __lib_framebuffer_init(lib_framebuffer *fb, const char *path) {|      ^~~~~~~~~~~~~~~~~~~~~~|      lib_framebuffer_init
D:\AWStudioProjects\awtk_clang_tidy_test\src\modules\lib_framebuffer.c:73:6: warning: invalid case style for function '__lib_framebuffer_destroy' [readability-identifier-naming]73 | void __lib_framebuffer_destroy(lib_framebuffer *fb) {|      ^~~~~~~~~~~~~~~~~~~~~~~~~|      lib_framebuffer_destroy

clang-tidy做了一个workaround, 允许用正则的方式去忽略匹配的命名样式。

注意必须是全字匹配,部分匹配是不起效的。

Checks: >readability-identifier-naming
CheckOptions:- key:             readability-identifier-naming.ClassCasevalue:           lower_case- key:             readability-identifier-naming.VariableCasevalue:           lower_case- key:             readability-identifier-naming.FunctionIgnoredRegexpvalue:           '__.*'- key:             readability-identifier-naming.ConstexprVariableCasevalue:           UPPER_CASE

参考

https://github.com/SCons/scons/issues/4637

https://scons.org/doc/latest/HTML/scons-user/ch27.html

https://lrita.github.io/2023/03/21/auto-clang-tidy-cpp-code/

相关文章:

尝试把clang-tidy集成到AWTK项目

前言 项目经过一段时间的耕耘终于进入了团队开发阶段&#xff0c;期间出现了很多问题&#xff0c;其中一个就是开会讨论团队的代码风格规范&#xff0c;目前项目代码风格比较混乱&#xff0c;有的模块是驼峰&#xff0c;有的模块是匈牙利&#xff0c;后面经过讨论&#xff0c;…...

04树 + 堆 + 优先队列 + 图(D1_树(D17_综合刷题练习))

目录 1. 二叉树的前序遍历&#xff08;简单&#xff09; 1.1. 题目描述 1.2. 解题思路 方法一&#xff1a;递归&#xff08;推荐使用&#xff09; 方法二&#xff1a;非递归&#xff08;扩展思路&#xff09; 2. 二叉树的中序遍历&#xff08;中等&#xff09; 2.1. 题目…...

C++ Primer 数组

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…...

Linux(Centos)安装allnnlp失败,jsonnet报错

Linux安装allnnlp失败,jsonnet报错 问题分析并解决1. 什么是 Software Collection (SCL)&#xff1f;2. 安装步骤2.1 安装 SCL 仓库支持2.2 安装 Devtoolset-7 提供的 C 编译器2.3 启用 Devtoolset-7 环境2.4 验证安装 3. 永久启用 Devtoolset-7 环境 问题 执行pip install al…...

小程序设计和开发:要如何明确目标和探索用户需求?

一、明确小程序的目标 确定业务目标 首先&#xff0c;需要明确小程序所服务的业务领域和目标。例如&#xff0c;是一个电商小程序&#xff0c;旨在促进商品销售&#xff1b;还是一个服务预约小程序&#xff0c;方便用户预订各类服务。明确业务目标有助于确定小程序的核心功能和…...

C#面试常考随笔12:游戏开发中常用的设计模式【C#面试题(中级篇)补充】

C#面试题&#xff08;中级篇&#xff09;&#xff0c;详细讲解&#xff0c;帮助你深刻理解&#xff0c;拒绝背话术&#xff01;-CSDN博客 简单工厂模式 优点&#xff1a; 根据条件有工厂类直接创建具体的产品 客户端无需知道具体的对象名字&#xff0c;可以通过配置文件创建…...

napalm ‘NXOSDriver‘ object has no attribute ‘port‘ 解决方案(随手记)

解决方案&#xff08;仍然使用ssh作为访问方式&#xff09; 使用napalm时&#xff0c;对于Cisco Nexus设备&#xff0c;默认采用的是443的api去访问获取数据&#xff0c;如果需要使用ssh的方式获取&#xff0c;需要特别指定get_network_driver(nxos_ssh) 使用443 https api的…...

顺序表与vector

一、顺序表的模拟实现 1.顺序表的实现方式 按照数组的申请方式&#xff0c;有以下两种实现方式&#xff1a; &#xff08;1&#xff09;数组采用静态分配&#xff0c;此时的顺序表称为静态顺序表。 &#xff08;2&#xff09;数组采用动态分配&#xff0c;此时的顺序表称为动…...

Spring 面试题【每日20道】【其三】

1、Spring 中的 Profile 注解的作用是什么&#xff1f; 中等 Profile 注解在Spring框架中用于根据不同的环境配置文件&#xff08;profiles&#xff09;来激活或忽略某些Bean的注册。它允许开发者定义逻辑以区分不同环境下的bean定义&#xff0c;例如开发、测试和生产环境。 …...

作业day7

请使用条件变量实现2生产者2消费者模型&#xff0c;注意1个生产者在生q产的时候&#xff0c;另外一个生产者不能生产 #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE]…...

后盾人JS--继承

继承是原型的继承 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </hea…...

基于CY8CKIT-149 BLE HID设备实现及PC控制功能开发(BLE HID+CapSense)

目录 项目介绍硬件介绍项目设计开发环境总体流程图BLE HID触摸按键与滑条 功能展示项目总结 &#x1f449; 【Funpack4-1】基于CY8CKIT-149 BLE HID设备实现及PC控制功能开发 &#x1f449; Github: EmbeddedCamerata/CY8CKIT-149_ble_hid_keyboard 项目介绍 基于英飞凌 CY8CK…...

工业级 激光测距 飞行时间法TOF 相位法 多频调制的本质!

激光测距仪中使用的相位法飞行时间&#xff08;Phase-based Time-of-Flight, ToF&#xff09;是一种通过测量调制光信号的相位差来计算距离的高精度方法。以下是详细解释&#xff1a; 一、核心原理&#xff1a;用“波的延迟”测距离 想象你向一堵墙拍手&#xff0c;通过回声的…...

014-STM32单片机实现矩阵薄膜键盘设计

1.功能说明 本设计主要是利用STM32驱动矩阵薄膜键盘&#xff0c;当按下按键后OLED显示屏上会对应显示当前的按键键值&#xff0c;可以将此设计扩展做成电子秤、超市收银机、计算器等需要多个按键操作的单片机应用。 2.硬件接线 模块管脚STM32单片机管脚矩阵键盘行1PA0矩阵键盘…...

flowable expression和json字符串中的双引号内容

前言 最近做项目&#xff0c;发现了一批特殊的数据&#xff0c;即特殊字符"&#xff0c;本身输入双引号也不是什么特殊的字符&#xff0c;毕竟在存储时就是正常字符&#xff0c;只不过在编码的时候需要转义&#xff0c;转义符是\&#xff0c;然而转义符\也是特殊字符&…...

关于Internet Download Manager(IDM)强制下载合并相关二次开发

目录 前言 强制下载视频 强制合并 迁移下载列表 免责声明 附录 前言 那个下载工具IDM不说了&#xff0c;确实有很多便捷的功能&#xff0c;不过也有一些限制 常见的包括但不限于&#xff1a; 1.无法下载有版权保护的视频&#xff08;不管真假&#xff09; 2.有时候下载…...

在Vue3 + Vite 项目中使用 Tailwind CSS 4.0

文章目录 首先是我的package.json根据官网步骤VS Code安装插件验证是否引入成功参考资料 首先是我的package.json {"name": "aplumweb","private": true,"version": "0.0.0","type": "module","s…...

Notepad++消除生成bak文件

设置(T) ⇒ 首选项... ⇒ 备份 ⇒ 勾选 "禁用" 勾选禁用 就不会再生成bak文件了 notepad怎么修改字符集编码格式为gbk 如图所示...

[mmdetection]fast-rcnn模型训练自己的数据集的详细教程

本篇博客是由本人亲自调试成功后的学习笔记。使用了mmdetection项目包进行fast-rcnn模型的训练&#xff0c;数据集是自制图像数据。废话不多说&#xff0c;下面进入训练步骤教程。 注&#xff1a;本人使用linux服务器进行展示&#xff0c;Windows环境大差不差。另外&#xff0…...

汽车自动驾驶AI

汽车自动驾驶AI是当前汽车技术领域的前沿方向&#xff0c;以下是关于汽车自动驾驶AI的详细介绍&#xff1a; 技术原理 感知系统&#xff1a;自动驾驶汽车通过多种传感器&#xff08;如激光雷达、摄像头、雷达、超声波传感器等&#xff09;收集周围环境的信息。AI算法对这些传感…...

信息学奥赛一本通 2101:【23CSPJ普及组】旅游巴士(bus) | 洛谷 P9751 [CSP-J 2023] 旅游巴士

【题目链接】 ybt 2101&#xff1a;【23CSPJ普及组】旅游巴士(bus) 洛谷 P9751 [CSP-J 2023] 旅游巴士 【题目考点】 1. 图论&#xff1a;求最短路Dijkstra, SPFA 2. 动态规划 3. 二分答案 4. 图论&#xff1a;广搜BFS 【解题思路】 解法1&#xff1a;Dijkstra堆优化 …...

Day25 洛谷 提高- 1007

零基础洛谷刷题记录 Day01 2024.11.18 Day02 2024.11.25 Day03 2024.11.26 Day04 2024.11.28 Day05 2024.11.29 Day06 2024 12.02 Day07 2024.12.03 Day08 2024 12 05 Day09 2024.12.07 Day10 2024.12.09 Day11 2024.12.10 Day12 2024.12.12 Day13 2024.12.16 Day14 2024.12.1…...

minikube 的 Kubernetes 入门教程--Ollama

让我们在本地 Kubernetes 中部署 Ollama&#xff08;llama3.2&#xff09; Ollama 安装方式有多样&#xff0c;是可选的&#xff0c;因为有minikube环境&#xff0c;我在 Kubernetes 中Deployment它。调用Ollama镜像&#xff0c;也可以用 Ollama CLI 去运行&#xff08;run&am…...

【字节青训营-7】:初探 Kitex 字节微服务框架(使用ETCD进行服务注册与发现)

本文目录 一、Kitex概述二、第一个Kitex应用三、IDL四、服务注册与发现 一、Kitex概述 长话短说&#xff0c;就是字节跳动内部的 Golang 微服务 RPC 框架&#xff0c;具有高性能、强可扩展的特点&#xff0c;在字节内部已广泛使用。 如果对微服务性能有要求&#xff0c;又希望…...

Docker技术相关学习三

一、Docker镜像仓库管理 1.docker仓库&#xff1a;用于存储和分发docker镜像的集中式存储库&#xff0c;开发者可以将自己创建的镜像推送到仓库中也可以从仓库中拉取所需要的镜像。 2.docker仓库&#xff1a; 公有仓库&#xff08;docker hub&#xff09;&#xff1a;任何人都可…...

浏览器查询所有的存储信息,以及清除的语法

要在浏览器的控制台中查看所有的存储&#xff08;例如 localStorage、sessionStorage 和 cookies&#xff09;&#xff0c;你可以使用浏览器开发者工具的 "Application" 标签页。以下是操作步骤&#xff1a; 1. 打开开发者工具 在 Chrome 或 Edge 浏览器中&#xf…...

生成式AI安全最佳实践 - 抵御OWASP Top 10攻击 (上)

今天小李哥将开启全新的技术分享系列&#xff0c;为大家介绍生成式AI的安全解决方案设计方法和最佳实践。近年来&#xff0c;生成式 AI 安全市场正迅速发展。据 IDC 预测&#xff0c;到 2025 年全球 AI 安全解决方案市场规模将突破 200 亿美元&#xff0c;年复合增长率超过 30%…...

mysql 学习8 函数,字符串函数,数值函数,日期函数,流程函数

函数 一 字符串函数 二 数值函数 三 日期函数 四 流程函数...

WSL2中安装的ubuntu搭建tftp服务器uboot通过tftp下载

Windows中安装wsl2&#xff0c;wsl2里安装ubuntu。 1. Wsl启动后 1&#xff09;Windows下ip ipconfig 以太网适配器 vEthernet (WSL (Hyper-V firewall)): 连接特定的 DNS 后缀 . . . . . . . : IPv4 地址 . . . . . . . . . . . . : 172.19.32.1 子网掩码 . . . . . . . .…...

7.2.背包DP

背包DP 在C中&#xff0c;背包动态规划&#xff08;Knapsack DP&#xff09; 是解决资源分配类问题的核心算法范式&#xff0c;尤其在处理物品选择与容量限制的组合优化问题时表现优异。以下是针对不同背包类型的详细解析与代码实现&#xff1a; 一、背包DP问题分类 类型特点…...

芝法酱学习笔记(2.6)——flink-cdc监听mysql binlog并同步数据至elastic-search和更新redis缓存

一、需求背景 在有的项目中&#xff0c;尤其是进销存类的saas软件&#xff0c;一开始为了快速把产品做出来&#xff0c;并没有考虑缓存问题。而这类软件&#xff0c;有着复杂的业务逻辑。如果想在原先的代码中&#xff0c;添加redis缓存&#xff0c;改动面将非常大&#xff0c…...

pandas习题 071:字典元素列表构造 DataFrame

(编码题)以下有一个列表嵌套字典 data,列表中的每个字典 fields 中的列表为每行数据的值,另有一个 col 为列名,利用这两个数据构造一个 DataFrame。 data = [{fields: [2024-10-07T21:22:01, USER-A, 21, 0,...

FFmpeg rtmp推流直播

文章目录 rtmp协议RTMP协议组成RTMP的握手过程RTMP流的创建RTMP消息格式Chunking(Message 分块) rtmp服务器搭建Nginx服务器配置Nginx服务器 librtmp库编译推流 rtmp协议 RTMP&#xff08;Real Time Messaging Protocol&#xff09;是由Adobe公司基于Flash Player播放器对应的…...

北京门头沟区房屋轮廓shp的arcgis数据建筑物轮廓无偏移坐标测评

在IT行业中&#xff0c;地理信息系统&#xff08;GIS&#xff09;是用于处理、分析和展示地理空间数据的重要工具&#xff0c;而ArcGIS则是GIS领域中的一款知名软件。本文将详细解析标题和描述中提及的知识点&#xff0c;并结合“门头沟区建筑物数据”这一标签&#xff0c;深入…...

Verilog基础(一):基础元素

verilog基础 我先说&#xff0c;看了肯定会忘&#xff0c;但是重要的是这个过程&#xff0c;我们知道了概念&#xff0c;知道了以后在哪里查询。语法都是术&#xff0c;通用的概念是术。所以如果你有相关的软件编程经验&#xff0c;那么其实开启这个学习之旅&#xff0c;你会感…...

Games104——游戏引擎Gameplay玩法系统:基础AI

这里写目录标题 寻路/导航系统NavigationWalkable AreaWaypoint NetworkGridNavigation Mesh&#xff08;寻路网格&#xff09;Sparse Voxel Octree Path FindingDijkstra Algorithm迪杰斯特拉算法A Star&#xff08;A*算法&#xff09; Path Smoothing Steering系统Crowd Simu…...

vue生命周期及其作用

vue生命周期及其作用 1. 生命周期总览 2. beforeCreate 我们在new Vue()时&#xff0c;初始化一个Vue空的实例对象&#xff0c;此时对象身上只有默认的声明周期函数和事件&#xff0c;此时data,methods都未被初始化 3. created 此时&#xff0c;已经完成数据观测&#xff0…...

数据分析师使用Kutools for Excel 插件

数据分析师使用Kutools for Excel 插件 Kutools for Excel 是一款功能强大的 Excel 插件&#xff0c;旨在提高 Excel 用户的工作效率&#xff0c;简化复杂的操作。它提供了超过 300 个增强功能&#xff0c;帮助用户快速完成数据管理、格式化、排序、分析等任务&#xff0c;特别…...

毫秒级响应的VoIP中的系统组合推荐

在高并发、低延迟、毫秒级响应的 VoIP 场景中&#xff0c;选择合适的操作系统组合至关重要。以下是针对 Ubuntu linux-lowlatency、CentOS Stream kernel-rt 和 Debian 自定义 PREEMPT_RT 的详细对比及推荐&#xff1a; 1. 系统组合对比 特性Ubuntu linux-lowlatencyCentO…...

unordered_map/set的哈希封装

【C笔记】unordered_map/set的哈希封装 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】unordered_map/set的哈希封装前言一. 源码及框架分析二.迭代器三.operator[]四.使用哈希表封装unordered_map/set后言 前言 哈…...

表格标签的使用

一.表格标签 1.1表格标签的作用 用来显示和展示数据&#xff0c;不是用来布局页面的。 1.2表格的基本语法 <table> //用于定义表格标签 <tr> // table row 用于定义表格中的行&#xff0c;必须嵌套在<table> </table>标签中 <td>单元格内的文…...

python算法和数据结构刷题[5]:动态规划

动态规划&#xff08;Dynamic Programming, DP&#xff09;是一种算法思想&#xff0c;用于解决具有最优子结构的问题。它通过将大问题分解为小问题&#xff0c;并找到这些小问题的最优解&#xff0c;从而得到整个问题的最优解。动态规划与分治法相似&#xff0c;但区别在于动态…...

【cocos creator】【模拟经营】餐厅经营demo

下载&#xff1a;【cocos creator】模拟经营餐厅经营...

编程AI深度实战:给vim装上AI

系列文章&#xff1a; 编程AI深度实战&#xff1a;私有模型deep seek r1&#xff0c;必会ollama-CSDN博客 编程AI深度实战&#xff1a;自己的AI&#xff0c;必会LangChain-CSDN博客 编程AI深度实战&#xff1a;给vim装上AI-CSDN博客 编程AI深度实战&#xff1a;火的编程AI&…...

信息学奥赛一本通 2088:【22CSPJ普及组】逻辑表达式(expr) | 洛谷 P8815 [CSP-J 2022] 逻辑表达式

【题目链接】 ybt 2088&#xff1a;【22CSPJ普及组】逻辑表达式(expr) 洛谷 P8815 [CSP-J 2022] 逻辑表达式 【题目考点】 1. 表达式树&#xff1a;中缀表达式建树 可以看该问题信息学奥赛一本通 1356&#xff1a;计算(calc) 了解中缀表达式建树过程。 【解题思路】 解法…...

Linux系统管理

文章目录 一、进程与服务二、systemctl基本语法操作 三、系统运行级别Linux进程运行级别查看当前运行级别修改当前运行级别 四、关机重启命令 一、进程与服务 守护进程与服务是一个东西。 二、systemctl 基本语法 systemctl start|stop|restart|status 服务名查看服务的方法…...

CTFSHOW-WEB入门-命令执行71-77

题目&#xff1a;web 71 题目&#xff1a;解题思路&#xff1a;分析可知highlight_file() 函数被禁了&#xff0c;先想办法看看根目录&#xff1a;cvar_export(scandir(dirname(‘/’))); 尝试一下发现很惊奇&#xff1a;&#xff08;全是&#xff1f;&#xff09;这种情况我也…...

[MRCTF2020]Ez_bypass1(md5绕过)

[MRCTF2020]Ez_bypass1(md5绕过) ​​ 这道题就是要绕过md5强类型比较&#xff0c;但是本身又不相等&#xff1a; md5无法处理数组&#xff0c;如果传入的是数组进行md5加密&#xff0c;会直接放回NULL&#xff0c;两个NuLL相比较会等于true&#xff1b; 所以?id[]1&gg…...

PPT演示设置:插入音频同步切换播放时长计算

PPT中插入音频&同步切换&放时长计算 一、 插入音频及音频设置二、设置页面切换和音频同步三、播放时长计算四、使用宏设置设置页面切换和音频同步一、 插入音频及音频设置 1.插入音频:点击菜单栏插入-音频-选择PC上的音频(已存在的音频)或者录制音频(现场录制) …...

Modbus Slave RTU 在 AVP28335(兼容德州仪器TMS 320 28335) 上实现含源码及注释。

今天先把题目先给出来&#xff0c; 在近两天会把源码 &#xff08;含详细注释 &#xff09;及部署、测试结果给出来&#xff0c; 希望能给大家帮助。&#xff08;原来这个程序在CSDN中&#xff0c;有小伙伴已经写了一些&#xff0c;但是发现里面埋了很多坑&#xff0c;例如&…...