修改 TensorFlow C API 的代码,使得固定的 saved_model.pb 文件名变为可设置的
我在 2021 年 5 月份发出了一个 SpleeterMsvcExe 开源项目,当时是支持 11kHz 和 16kHz 两种模型,年底的时候又添加了对 22kHz 模型的支持。这三种不同频率上限的模型, variables 目录中的文件是完全相同的,只有 saved_model.pb 不同。但 TensorFlow 源码中这个文件名在 constants.h 中被定义为了固定值:

  1. // SavedModel proto filename.
  2. constexpr char kSavedModelFilenamePb[] = "saved_model.pb";
  3.  
  4. // SavedModel text format proto filename.
  5. constexpr char kSavedModelFilenamePbTxt[] = "saved_model.pbtxt";

用于加载模型的 ReadMetaGraphDefFromSavedModel() API 函数会调用 reader.cc 中的 ReadSavedModel() 函数:

  1. Status ReadSavedModel(const string& export_dir, SavedModel* saved_model_proto) {
  2.   LOG(INFO) << "Reading SavedModel from: " << export_dir;
  3.  
  4.   const string saved_model_pb_path =
  5.       io::JoinPath(export_dir, kSavedModelFilenamePb);
  6.   if (Env::Default()->FileExists(saved_model_pb_path).ok()) {
  7.     return ReadBinaryProto(Env::Default(), saved_model_pb_path,
  8.                            saved_model_proto);
  9.   }
  10.   const string saved_model_pbtxt_path =
  11.       io::JoinPath(export_dir, kSavedModelFilenamePbTxt);
  12.   if (Env::Default()->FileExists(saved_model_pbtxt_path).ok()) {
  13.     return ReadTextProto(Env::Default(), saved_model_pbtxt_path,
  14.                          saved_model_proto);
  15.   }
  16.   return Status(error::Code::NOT_FOUND,
  17.                 "Could not find SavedModel .pb or .pbtxt at supplied export "
  18.                 "directory path: " +
  19.                     export_dir);
  20. }

可以看到完全没有考虑让这个文件名可以被指定,而且一直到现在最新的 v2.12.0 版本都是这样的。

我之前曾经考虑过从模型下手,试图把三种种不同频率上限的模型合并为一个。搜索过几次 tensorflow saved model merge 之类的关键词,也借助 ChatGPT 修改过 checkpoint 到 saved model 的转换脚本,最终都没成功。而且从把 saved_model.pb 转换为 .pbtext 格式的结果看,整个 protobuf 文件中和频率上限相关的参数非常多,而且看文件里这些参数所在位置,也没法合并。

之前一直想尽量用官方提供的二进制版本,是考虑到这对于杀毒软件比较友好,自己编译的会有全新的 hash 值,有误报的风险。但现在考虑到 SpleeterMsvcExe, 即将发出的 WPF 版 Spleeter GUI 以及 BeatShow Player 程序的易用性,还是打算对 TensorFlow 的源码进行修改,自行编译一个版本来用了。

只是这一点需求,代码还是很好改的。直接在 ReadSavedModel() 中添加一段识别和处理环境变量的代码就可以了:

  1. Status ReadSavedModel(const string& export_dir, SavedModel* saved_model_proto) {
  2.   LOG(INFO) << "Reading SavedModel from: " << export_dir;
  3.  
  4.   const char* tf_alt_saved_model_pb = getenv("TF_ALT_SAVED_MODEL_PB");
  5.   if (tf_alt_saved_model_pb != nullptr) {
  6.     LOG(INFO) << "Environment variable TF_ALT_SAVED_MODEL_PB is set: " << tf_alt_saved_model_pb;
  7.     const string alt_saved_model_pb_path =
  8.         io::JoinPath(export_dir, tf_alt_saved_model_pb);
  9.     if (Env::Default()->FileExists(alt_saved_model_pb_path).ok()) {
  10.       LOG(INFO) << "Will use " << tf_alt_saved_model_pb << " instead of saved_model.pb";
  11.       return ReadBinaryProto(Env::Default(), alt_saved_model_pb_path,
  12.                              saved_model_proto);
  13.     } else {
  14.       return Status(error::Code::NOT_FOUND,
  15.                     "Could not find the specified .pb file: " + alt_saved_model_pb_path);
  16.     }
  17.   }
  18.  
  19.   const string saved_model_pb_path =
  20.       io::JoinPath(export_dir, kSavedModelFilenamePb);

不用改 API 接口的定义,兼容性和灵活性都比较好。程序中调用 TensorFlow C API 前,设置一下 TF_ALT_SAVED_MODEL_PB 环境变量的值就可以了。

对于 TensorFlow 的编译过程,可以参考上一篇文章:
TensorFlow C API 动态库 v1.15 版本的编译过程

2023-04-22 添加:

修改过的项目已经发到了 GitHub 上了: https://github.com/wudicgi/tensorflow-mod

实际的修改和之前贴的有差异,具体修改可以看 c5cfda2 这个提交。也可以直接下载 release 版本使用: https://github.com/wudicgi/tensorflow-mod/releases/tag/v1.15.5-mod.1
Current language: 中文 (简体)
TensorFlow C API 动态库 v1.15 版本的编译过程

整个编译工程基本参照官方的 在 Windows 环境中从源代码构建 说明就可以完成。之前搜到一些更早的版本是用 Cmake + Visual Studio 编译的,貌似编译过程中容易产生一些问题。TensorFlow v1 的最后一个版本 1.15.5 是是用 Bazel 进行构建的,它会直接调用 MSVC 编译器,整个编译过程还是比较顺利的。

经过测试的构建配置一节中, tensorflow-1.15.0 编译时使用的 Python 版本为 3.5-3.7, 编译器为 MSVC 2017, 构建工具为 Bazel 0.26.1. 下面会使用这些推荐的版本。

1. 安装 Python 和 TensorFlow 软件包依赖项

我的电脑上已安装有 Python 3.7 和 3.9, 但是 PATH 中 3.9 的目录在前边,因此先调整顺序将 3.7 放到前边。不调整的话,调用 Python 前通过以下命令临时设置一下环境变量也可以:

set PATH=C:\Python\Python37-64\Scripts;C:\Python\Python37-64;%PATH%

因为 v1.15.5 版本的 /tensorflow/tools/pip_package/setup.py 文件里有 'keras_applications >= 1.0.8', 因此我将官方构建说明中 keras_applications 的安装版本由 1.0.6 改为了 1.0.8, 执行:

pip3 install six numpy wheel
pip3 install keras_applications==1.0.8 --no-deps
pip3 install keras_preprocessing==1.0.5 --no-deps

2. 安装 Bazel

我电脑上没有 Bazelisk, 也没有 Chocolatey 和 Scoop, 因此我选择手动安装 Bazel.

https://github.com/bazelbuild/bazel/releases/tag/0.26.1 下载 bazel-0.26.1-windows-x86_64.zip, 把它解压到任意一个目录,并且把这个目录加入到 PATH 中后, Bazel 就可以使用了。

首次运行 bazel.exe 时,它会解压一些文件到 C:\Users\%USERNAME% 目录下,之后编译时生成的一些中间文件也会在用户的 Profile 目录下。

3. 安装 MSYS2

MSYS2 的安装可以参考 https://www.msys2.org/ 首页的说明。我的电脑上已经安装有 MSYS2 了,就只执行了一下 pacman:

pacman -S git patch unzip

并且确保 C:\msys64\usr\bin 在 PATH 中。

4. 安装 Visual Studio

我的电脑已安装有 Visual Studio 2017 和 2022.

如果没有安装任何版本的 Visual Studio, 要安装 VS 2017 可以到 https://visualstudio.microsoft.com/zh-hans/vs/older-downloads/ 下载,可能需要登录 Microsoft 账号。

如果已经安装有 VS 2019, 2022 等更高版本,建议先尝试是否能正常编译。在已安装高版本 VS 的情况下安装低版本 VS 可能会有问题。

5. 下载 TensorFlow 源代码

使用 TortoiseGit 克隆版本库 https://github.com/tensorflow/tensorflow.git, 分支填写 v1.15.5, 深度填写 1.

6. 配置 build

在克隆得到的 tensorflow 目录中,执行:

python ./configure.py

如果不需要 GPU 支持,可以所有问题都选择默认值。

7. 构建 C API 库文件

执行以下命令构建 C API 的 dll 和 lib 文件:

bazel build --config=v1 --config=opt //tensorflow:tensorflow.dll
bazel build --config=v1 --config=opt //tensorflow:tensorflow.lib

在我的 5900X 上整个编译过程耗时约 14 分钟,生成文件大小和官方提供的 libtensorflow-cpu-windows-x86_64-1.15.0.zip 基本相同。

2023-04-22 添加:

编译时可以使用命令:

bazel build --config=v1 --copt=/arch:AVX --copt=/arch:AVX2 tensorflow:tensorflow.dll tensorflow:tensorflow_dll_import_lib

这样可以忽略掉为 opt config 配置的编译参数,直接指定编译支持 AVX 和 AVX2 的版本 (执行 python ./configure.py 时, opt config 的编译参数默认只有 /arch:AVX, 会缺少对 AVX2 的支持)。
Current language: 中文 (简体)
入手 RTX 4070 显卡
去年夏天装的 AMD 5900X 的电脑,因为显卡价格原因没有买新显卡,而是接着用 2016 年买的 1050ti 显卡。6 年前的显卡显然已经非常不够用了,除了运算速度慢, 4GB 显存有些现在流行的开源项目根本跑不了。

前两天看到 4070 显卡发售了,查了下非常适合我用。华硕有一个双风扇的 DUAL GeForce RTX4070-12G 显卡,长度 27cm, 不用换机箱了。最大功耗 200W, 现在用的 750W 的电源也不用换了。为了保险又等了两天,稍微查了下 40 系另外 3 个型号的区别,在不需要 4090 算力的情况下, 4080 和 4090 比毫无性价比, 4070ti 比 4070 又强得有限,基本不用犹豫了。

今天上午下单,下午收到显卡。装上后简单测了下性能提升,和新的超分辨率功能。

性能提升

Whisper 的 ggml-large.bin 模型试了一下,之前用 1050ti 对一个 06:24 (384 秒) 的音频文件进行处理,耗时 160 秒:

Using GPU "NVIDIA GeForce GTX 1050 Ti", feature level 12.1, effective flags Wave32 | NoReshapedMatMul
Loaded MEL filters, 62.8 kb RAM
Loaded vocabulary, 51865 strings, 3037.1 kb RAM
Loaded 1259 GPU tensors, 2950.66 MB VRAM
Loaded model from "E:\Softwares\whisper-bin-x64\models\ggml-large.bin" to VRAM
Created source reader from the file "E:\CloudMusic\Nikita Fomin\Stranger (Dream Version).mp3"
[00:00:00.000 --> 00:00:03.480]   [MUSIC PLAYING]
...
[00:06:20.600 --> 00:06:23.600]   [MUSIC PLAYING]
    CPU Tasks
LoadModel       2.77043 seconds
RunComplete     161.1 seconds
Run     160.168 seconds
...
    GPU Tasks
LoadModel       2.10459 seconds
Run     160.019 seconds
...

换成 4070 显卡后,耗时 26 秒:

Using GPU "NVIDIA GeForce RTX 4070", feature level 12.1, effective flags Wave32 | NoReshapedMatMul
Loaded MEL filters, 62.8 kb RAM
Loaded vocabulary, 51865 strings, 3037.1 kb RAM
Loaded 1259 GPU tensors, 2950.66 MB VRAM
Computed CPU base frequency: 3.69996 GHz
Loaded model from "E:\Softwares\whisper-bin-x64\models\ggml-large.bin" to VRAM
Press Control+C or Control+Break to close this window
Created source reader from the file "E:\CloudMusic\Nikita Fomin\Stranger (Dream Version).mp3"
[00:00:00.000 --> 00:00:03.480]   [MUSIC PLAYING]
...
[00:06:20.600 --> 00:06:23.600]   [MUSIC PLAYING]
    CPU Tasks
LoadModel       1.56206 seconds
RunComplete     27.2146 seconds
Run     26.2726 seconds
...
    GPU Tasks
LoadModel       917.515 milliseconds
Run     26.1418 seconds
...

仅就这个应用来说,处理速度是之前的 6.15 倍。

超分辨率 (RTX Video Super Resolution) 功能

https://downloads.videolan.org/testing/vlc-rtx-upscaler/

https://github.com/emoose/VideoRenderer/releases/tag/rtx-1.1

https://www.videohelp.com/software/MPC-BE

K-Lite Mega Codec Pack 17.0.0, 2022-6-8

MPC-HC (Nightly, 64-bit)
------------------------

Build information:
    Version:            1.9.21.24 (1c7cdd24a)
    Compiler:           MSVC v19.29.30143
    Build date:         May 12 2022

LAV Filters:
    LAV Splitter:       0.76.1.3
    LAV Video:          0.76.1.3
    LAV Audio:          0.76.1.3
    FFmpeg compiler:    MinGW-w64 GCC 10.2.0

Operating system:
    Name:               Windows 10 (Build 21H2)
    Version:            10.0.19045 (64-bit)

Hardware:
    CPU:                AMD Ryzen 9 5900X 12-Core Processor
    GPU:                NVIDIA GeForce RTX 4070 (driver version: 31.0.15.3161)
Current language: 中文 (简体)
Wrote a quick launch bar for Windows 10 taskbar, solving the alignment and scaling problem
On Windows 10, when you choose to use small button on taskbar, the icons in quick launch bar will have alignment and scaling problem:



So I wrote a custom quick launch bar based on WPF and DeskBand, which can correctly deal with these problems:



The GitHub project is at https://github.com/wudicgi/Win10QuickLaunchBar

If needed, you can download the release package to use.
Current language: English · 其他语言: 中文 (简体)
搞了一台 DIY 级别的雕刻机 CNC 3018
前两台在淘宝上以不到 650 的价格,购买了一个 CNC 3018 雕刻机的套件 (其中脱机控制器占 90 元)。
到货后这一盒子东西还有点分量:



除了主轴电机部分是装配好的一个整体,其他所有部分全是散件:



需要自己根据说明书完成组装:





实际装配时还看了下厂家在 ytb 上发的视频,装的顺序不对的话,对齐的时候会困难一些:



大约耗时两个晚上组装完成,看起来是这样的:



本来加工平台应该是居中放置的,我特意把它靠左放了。这是在购买前就考虑好的,目的是为了利用右侧的空间,来对塑料外壳的上下侧面进行加工。否则按这台 DIY 机器只有 45mm 的 Z 轴行程,塑料外壳根本没法立着固定在平台上来加工。



用脱机控制器简单铣了个方孔,感觉还不错。即使是不画图纸,临时手动随便开个孔,也比以前用电钻支架+十字工作台手动操作要方便一些。



当然这个之后准备是用 3D 打印制作好塑料外壳的夹具后,由 PC 来控制自动进行加工的。
Current language: 中文 (简体)
Bought a Sermoon V1 3D printer, and tried making the first self-designed part
Before the Spring Festival, I bought a 3D printer of Creality, model Sermoon V1:



I chose this model mainly because it just fits on my shelf. With the Core XY structure, it need no extra space to move back and forth the printing platform:



Below is what the attached whistle example prints out:



Today I designed a fixing accessories for a small electric grinder in Fusion 360. The software provided by Creality is easy to use. I did not change most of the default parameters, and the first try is successful:




Current language: English · 其他语言: 中文 (简体)
Released an open source Spleeter command line program, written in pure C language
View on GitHub: https://github.com/wudicgi/SpleeterMsvcExe



SpleeterMsvcExe is a Windows command line program for Spleeter, which can be used directly.

It is written in pure C language, using ffmpeg to read and write audio files, and using Tensorflow C API to make use of Spleeter models. No need to install Python environment, and it does not contain anything related to Python.

Furthermore, SpleeterMsvcExe reduced the memory usage through segmented processing, hence it can handle single audio file over 30 minutes. Due to the length-extending process, all segments can be concatenated seamlessly.
Current language: English · 其他语言: 中文 (简体)
When We Disco 的 BeatShow 演示
最近被这首歌洗脑了,忍不住又录了个 demo. 现在业余时间一直在完善这个项目,主要功能基本都完成了,还剩下一些收尾工作,应该很快就能公开提供下载了。


在优酷上观看: https://v.youku.com/v_show/id_XNDg0ODgwMTMyMA==.html
在 bilibili 上观看: https://www.bilibili.com/video/BV1N64y1F73D
Current language: 中文 (简体)
CH552 官方 USB HID 示例程序中的一处 bug
最近在开发 BeatShow 设备的固件恢复功能时发现,使用 CH552 实现的 USB HID 设备总是获取不到设备名称,而 STM32F072 的就没问题。即使将所有 USB 描述符都改成一样的,问题也仍然存在。

调试 PC 端程序看到底层调用的 HidD_GetProductString() 函数执行结果是成功的,只是返回的字符串长度为 0, 所以就得从硬件下手了。

经过抓包,发现对于 CH552 设备,除了设备枚举过程中的正常 GET_DESCRIPTOR 请求,在 PC 端程序调用 HidD_GetProductString() 时系统又发送了额外的 GET_DESCRIPTOR 请求。而且这些请求的返回数据长度只有 2 字节,明显是不正常的。

CH552 设备枚举过程中正常的 GET_DESCRIPTOR 请求, wLength = 0x00FF = 255:


CH552 设备完成枚举后的额外 GET_DESCRIPTOR 请求, wLength = 0x0102 = 258:


STM32 设备在完成枚举后则没有这些额外的 GET_DESCRIPTOR 请求:


对于为什么系统只对 CH552 设备发送了这些额外的 GET_DESCRIPTOR 请求,经过一番搜索没有找到答案。但显然 CH552 的程序在这块的处理上有 bug。

CH552 实现的 bootloader 是以 WCH 官方的 USB HID 示例程序 CompatibilityHID.C 为基础,逐步重构和修改而来的。这个示例中有一段处理 SETUP 事物的代码是这样的:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...

可以看到只取了 wLength 的低字节部分 wLengthL 作为 SetupLen, 完全没有使用高字节部分 wLengthH。而另一个 VendorDefinedDev.C 示例程序中,就对高字节部分 wLengthH 做了检查:

case UIS_TOKEN_SETUP | 0:                       // endpoint 0# SETUP
    len = USB_RX_LEN;
    if (len == sizeof(USB_SETUP_REQ)) {         // SETUP 包长度
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH || SetupLen > 0x7F) SetupLen = 0x7F;      // 限制总长度
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReqCode = UsbSetupBuf->bRequest;
        // ...

CompositeKM.C 示例程序中也一样检查了 wLengthH:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH || SetupLen > 0x7F)
        {
            SetupLen = 0x7F;                    // 限制总长度
        }
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...

CompatibilityHID.C 示例程序中原本没有任何字符串描述符,也没有对 GET_DESCRIPTOR 请求的处理代码,相关的内容都是我后添加的。这可能是官方程序中这个 bug 存在了这么长时间的原因。

知道原因后就好修改了,只要在 wLengthH 不为 0 时把 SetupLen 限制到 uint8_t 范围内的最大值 0xFF 就可以了:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH != 0)
        {
            SetupLen = 0xFF;                    // 限制总长度
        }
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...
Current language: 中文 (简体)
简单“修正”了一下 AStyle 中的一个重复缩进的问题
GitHub 地址: https://github.com/wudicgi/astyle-modified
效果对比脚本: https://github.com/wudicgi/astyle-modified/tree/master/bin
修改后程序下载: https://github.com/wudicgi/astyle-modified/blob/master/bin/AStyle.exe

1. 发现问题

上周我在对 ffmpeg 的 transcoding.c 示例程序进行代码风格美化时,发现 AStyle 在很多处不需要改动的地方添加了额外的缩进:

// 处理前
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
        args, NULL, filter_graph);

// 处理后
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                args, NULL, filter_graph);

其实以前也遇到过这个问题,但这次出现得比较集中、比较多,就准备解决一下了。打开 AStyle 的 VS2017 项目进行调试,发现对于我使用的选项

indent=spaces=4
indent-after-parens
indent-continuation=2

AStyle 在遇到赋值运算符 '=' 时为后续行添加 2 级缩进,而当继续处理遇到左括号 '(' 时又添加了 2 级缩进,于是后续行就拥有了多余的 2 级缩进。

2. 修改问题

我使用了一个简单粗暴的方式进行修改,就是在 ASBeautifier 类的 registerContinuationIndent() 方法中添加一个条件判断,使它在已经因为 '=' 添加了后续行缩进的情况下,不再因为其后出现的第一个 '(' 再添加缩进。

void ASBeautifier::registerContinuationIndent(const string& line, int i, int spaceIndentCount_,
                                              int tabIncrementIn, int minIndent, bool updateParenStack)
{
    assert(>= -1);
    int remainingCharNum = line.length() - i;
    int nextNonWSChar = getNextProgramCharDistance(line, i);

    // if indent is around the last char in the line OR indent-after-paren is requested,
    // indent with the continuation indent
    if (nextNonWSChar == remainingCharNum || shouldIndentAfterParen)
    {
        // added by Wudi
        bool noDuplicatedIndentForFirstParen = updateParenStack // current indentation is for opening paren '('
                && !continuationIndentStack->empty()            // previously indented for an assignment '='
                && parenIndentStack->empty()                    // current '(' is the first opening paren needs to add indentation
                && (!= 0);                                    // current '(' must not be the first char, otherwise there is no '=' ahead

        int previousIndent = spaceIndentCount_;
        if (!continuationIndentStack->empty())
            previousIndent = continuationIndentStack->back();

        int currIndent = continuationIndent * indentLength + previousIndent;

        // added by Wudi
        if (noDuplicatedIndentForFirstParen) {
            currIndent = previousIndent;
        }

        if (currIndent > maxContinuationIndent && line[i] != '{')
            currIndent = indentLength * 2 + spaceIndentCount_;
        continuationIndentStack->emplace_back(currIndent);
        if (updateParenStack)
            parenIndentStack->emplace_back(previousIndent);
        return;
    }

    // ...

3. 修改前后效果对比

对同样的一段代码进行处理,原版 AStyle 的输出为:

int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                args, NULL, filter_graph);

int (*enc_func_2)(AVCodecContext *, AVPacket *, const AVFrame *, int *) = (ifmt_ctx->streams[stream_index]->codecpar->codec_type ==
                AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;

ret = func(a, b, another_func(1, 2,
                        3),
                c, d);

修改后版本的输出为:

int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
        args, NULL, filter_graph);

int (*enc_func_2)(AVCodecContext *, AVPacket *, const AVFrame *, int *) = (ifmt_ctx->streams[stream_index]->codecpar->codec_type ==
        AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;

ret = func(a, b, another_func(1, 2,
                3),
        c, d);

4. 修改的副作用

标题中的“修正”带引号是因为这并不一定是个 bug, 同时现在所做的这个修改会有一些副作用。对于一些极端情况的代码,例如:

int ret = func(a, b, another_func(1, 2,
3, 4, 5),
c, d)
/ 123;

原版 AStyle 能保留所有缩进等级,输出结果为:

int ret = func(a, b, another_func(1, 2,
                        3, 4, 5),
                c, d)
        / 123;

而该修改版本的输出为:

int ret = func(a, b, another_func(1, 2,
                3, 4, 5),
        c, d)
        / 123;

最外层在缩进等级上没有与内层区分开来。但像这样的代码并不常见,所以为方便自己日常使用,“修正”一下还是有必要的。

附: AStyle 原版链接

AStyle 的官方项目地址为 http://astyle.sourceforge.net/
Current language: 中文 (简体)
More entries: [1] [2] [3] [4] [5] [6] ... [17]
« Previous page · Next page »