快速搭建一个 C++ Playground

如果你是一个 C++新手(on linux),你一定尝试过徒手用 vim 写 cpp 文件,保存并退出,执行 g++编译源文件,执行编译出来的可执行文件,查看输出结果(如果你还没有尝试过,并且觉得自己还是个新手,请务必尝试一下)。久而久之,你愈发熟练,甚至配置了 YCM 大杀器,多开终端(甚至是多开 vim tab 页)同时编写多个 cpp 文件,然后在一个终端里面执行编译,输出结果。你觉得 IDE 太过臃肿,像什么 Visual Studio, Clion 之列的难以入你法眼。而 vim 什么的又太过原始,而且配置起来稍显费力(如果你是高玩,相信此文对你没有帮助),你一直想找一个配置简单,能够专心写一些玩具程序的 demo 开发工具。

恭喜你,此文将解决你的问题。

VSCode 和它的伙伴们

首先准备好 VSCode 以及必要插件:

yychi@~> code --list-extensions
bungcip.better-toml
huacnlee.autocorrect
huizhou.githd
KylinIDETeam.cmake-intellisence
llvm-vs-code-extensions.vscode-clangd #1
mhutchie.git-graph
tomoki1207.pdf
twxs.cmake #2
vadimcn.vscode-lldb #3
vscodevim.vim
  1. clangd 用于补全提示跳转等常用功能

  2. cmake 语法高亮(可选)

  3. lldb 调试工具[1]

开始配置

mkdir ~/code_scratch # 新建playground目录,随便取个名字:
cd ~/code_scratch
mkdir scratch
touch CMakeLists.txt
touch scratch/hello.cpp
code ~/code_scratch  # vscode打开该目录作为工作目录

在工作目录中创建 scratch 目录,用于存放写着玩的 cpp 文件(其实是刷题用的 ;)。然后在主目录下创建文件 CMakeLists.txt.

完成之后的目录结构长这样:

yychi@~/code_scratch> tree
.
├── CMakeLists.txt
└── scratch
    └── hello.cpp

2 directories, 2 files

下面是两个文件的内容:

// file: hello.cpp

#include <iostream>
using namespace std;
int main()
{
    cout << "hello playground!" << endl;
    return 0;
}
# file: CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(scratch)

message(STATUS "CMake version: " ${CMAKE_VERSION})
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.2")
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
else()
    message(STATUS "Checking compiler flags for C++11 support.")
    # Set C++11 support flags for various compilers
    include(CheckCXXCompilerFlag)
    check_cxx_compiler_flag("-std=c++11" COMPILER_SUPPORTS_CXX11)
    check_cxx_compiler_flag("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
    if(COMPILER_SUPPORTS_CXX11)
        message(STATUS "C++11 is supported.")
        if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++")
        else()
            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
        endif()
    elseif(COMPILER_SUPPORTS_CXX0X)
        message(STATUS "C++0x is supported.")
        if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -stdlib=libc++")
        else()
            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
        endif()
    else()
        message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
    endif()
endif()


 
# 禁止 C++ assert terminate 程序
# add_definitions(-DNDEBUG)

# ASIO 不使用 Boost 库
#add_definitions(-DASIO_STANDALONE)

# Spdlog 使用外部 Fmt 库
#add_definitions(-DSPDLOG_FMT_EXTERNAL)

# Jwt-cpp 使用外部 Json 库
# ADD_DEFINITIONS(-DJWT_CPP_JSON_EXTERNAL)

# add_definitions(-DGLOG_ON)

# 生成编译命令文件,给 YCM 使用
# set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

ADD_COMPILE_OPTIONS(-g)

#设置输出目录
# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/build/)

#加入包含目录
#include_directories(${CMAKE_CURRENT_SOURCE_DIR}/log/src) 


#静态库目录
#link_directories(/usr/lib64/mysql)

#logger lib 
#add_subdirectory(log)

file(GLOB_RECURSE demo_srcs scratch/*.cpp)
message(STATUS "listing source...\n" ${demo_srcs})
foreach(srcfile IN LISTS demo_srcs)
    get_filename_component(elfname ${srcfile} NAME_WE)
    message(STATUS "compile ${srcfile} ..to.. ${elfname}")
    add_executable(${elfname} ${srcfile})
endforeach()


# aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRCS) #加入目录下所有源码
# add_executable(demo ${SRCS}) #生成可执行文件
# target_link_libraries(demo logger)  #链接 logger 库

# add_executable(topk topk.cpp)
# add_executable(write write.cpp addressbook.pb.cc)
# add_executable(read addressbook.pb.cc read.cpp)
# target_link_libraries(write protobuf)
# target_link_libraries(read protobuf)
# aux_source_directory(test_link test_link_src)
# add_executable(test_link ${test_link_src})

然后执行下列操作:

yychi@~/code_scrach> mkdir build
yychi@~/code_scrach> cd build
yychi@~/code_scrach/build> cmake ..
-- The C compiler identification is GNU 13.2.1
-- The CXX compiler identification is GNU 13.2.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- CMake version: 3.27.1
-- listing source...
/home/yychi/code_scrach/scratch/hello.cpp
-- compile /home/yychi/code_scrach/scratch/hello.cpp ..to.. hello
-- Configuring done (0.9s)
-- Generating done (0.0s)
-- Build files have been written to: /home/yychi/code_scrach/build
yychi@~/code_scrach/build> make hello
[ 50%] Building CXX object CMakeFiles/hello.dir/scratch/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
yychi@~/code_scrach/build> ./hello
hello playground!

这样,后续需要写一个玩具程序,只要在 scratch 目录下新建一个 cpp 文件,然后在 build 目录下 cmake & make 一下,就得到了编译后的可执行文件。是不是很赞!

调试配置

虽然对于玩具程序,cout, printf 通常足够定位问题,但是对于复杂的程序,我还是希望可以调试的。

这一点,通过 code-lldb 插件也可以做到。在工作目录下创建.vscode/launch.json,或者直接按 F5 就会自动生成该文件。配置如下:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    // For variable references, visit: https://code.visualstudio.com/docs/editor/variables-reference
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "DebugFile",
            "program": "build/${fileBasenameNoExtension}",
            "args": [],
            "cwd": "${workspaceFolder}"
        }
    ]
}

这样,就可以调试当前编辑器 focus 的文件了,当然,首先得编译输出可执行文件。效果如下:

远程调试

此外,lldb 还支持远程调试,将 lldb-server 及相关文件拷贝到目标机器,在远程机器上执行 lldb-server 起监听,然后在本机配置远程调试任务(上述 launch.json 中的第二个就是用于远程调试)。然后,当你执行这个调试任务,其实就是将可执行文件拷贝到远程机器上执行,并启用调试功能。

此外远程调试还有一个 bonus,可以 root 权限进行调试!只需使用 root 权限执行 lldb-server 即可。

远程调试的配置:

// file:///.vscode/launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    // for variables see: https://code.visualstudio.com/docs/editor/variables-reference
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug",
            "args": [],
            "cwd": "${workspaceFolder}/service/src/main/cpp",
            "program": "service/src/main/cpp/build/${fileBasenameNoExtension}",
        },
        {
            // see: https://github.com/vadimcn/codelldb/blob/master/MANUAL.md#connecting-to-lldb-server-agent
            // and https://github.com/vadimcn/codelldb/discussions/779
            "type": "lldb",
            "request": "launch",
            "name": "RemoteLaunch",
            "args": [],
            "program": "service/src/main/cpp/build/${fileBasenameNoExtension}",
            "initCommands": [
                "platform select remote-linux",
                "platform connect connect://127.0.0.1:12306", // 此处可更改为远程 ip
            ],
            "expressions": "native",
        }
    ]
}

clangd 的配置:

// file:///.vscode/settings.json
{
    "clangd.path": "/usr/bin/clangd",
    "clangd.arguments": [
        // 在后台自动分析文件(基于 complie_commands)
        "--background-index",
        // 标记 compelie_commands.json 文件的目录位置
        // 关于 complie_commands.json 如何生成可见我上一篇文章的末尾
        // https://zhuanlan.zhihu.com/p/84876003
        // "--compile-commands-dir=build",
        // 同时开启的任务数量
        "-j=12",
        // 告诉 clangd 用那个 clang 进行编译,路径参考 which clang++的路径
        "--query-driver=/usr/bin/g++",
        // clang-tidy 功能
        "--clang-tidy",
        "--clang-tidy-checks=performance-*,bugprone-*",
        // 全局补全(会自动补充头文件)
        "--all-scopes-completion",
        // 更详细的补全内容
        "--completion-style=detailed",
        // 补充头文件的形式
        "--header-insertion=iwyu",
        // pch 优化的位置
        "--pch-storage=disk",
    ],
}

References

  1. https://github.com/vadimcn/codelldb/discussions/779

  2. https://github.com/vadimcn/codelldb/blob/master/MANUAL.md#connecting-to-lldb-server-agent

  3. https://windowsmacos-vscode-c-llvm-clang-clangd-lldb.readthedocs.io/configure.html