跳转至

笔记

Intel Pin

简介

Pin 是一个动态二进制插桩工具:

  • 支持 Linux, macOS 和 Windows 操作系统以及可执行程序。
  • Pin可以通过pintools在程序运行期间动态地向可执行文件的任意位置插入任意代码(C/C++),也可以attach到一个正在运行的进程。
  • Pin 提供了丰富的API,可以抽象出底层指令集特性,并允许将进程的寄存器数据等的上下文信息作为参数传递给注入的代码。Pin会自动存储和重置被注入代码覆盖的寄存器,以恢复程序的继续运行。对符号和调试信息也可以设置访问权限。
  • Pin内置了大量的样例插桩工具的源码,包括基本块分析器、缓存模拟器、指令跟踪生成器等,根据自己的实际需求进行自定义开发也十分方便。

特点

  • 只是它的输入不是字节码,而是可执行文件的执行汇编代码(机械码)。
  • Pin 对所有实际执行的代码进行插桩,但是如果指令没有被执行过,就一定不会被插桩。
  • 工作在操作系统之上,所以只能捕获用户级别的指令。
  • 由于Pintool的编译十分自由,Pin不打算提供多线程插桩的支持。
  • 同时有3个程序运行:应用程序本身、Pin、Pintool。
  • 应用程序是被插桩的对象、Pintool包含了如何插桩的规则(用户通过API编写的插入位置和内容)
  • 三者共享同一个地址空间,但不共享库,避免了冲突。 Pintool 可以访问可执行文件的全部数据,还会与可执行文件共享 fd 和其他进程信息。

pin

基本原理

Pin机制类似Just-In-Time (JIT) 编译器,Trace插桩的基本流程(以动态基本块BBLs为分析单位):

  • Pin 会拦截可执行文件的第一条指令,然后对从该指令开始的后续的指令序列重新“compile”新的代码,并执行
  • Pin 在分支退出代码序列时重新获得控制权限,基于分支生成更多的代码,然后继续运行。

动态基本块BBLs

通过一个例子来说明动态基本块BBLs与 汇编代码的BB的区别

switch(i)
{
    case 4: total++;
    case 3: total++;
    case 2: total++;
    case 1: total++;
    case 0:
    default: break;
}

上述代码会编译成下面的汇编, 对于实际执行时跳转从.L7进入的情况,BBLs包括四条指令,但是BB只会包括一条。

.L7:
        addl    $1, -4(%ebp)
.L6:
        addl    $1, -4(%ebp)
.L5:
        addl    $1, -4(%ebp)
.L4:
        addl    $1, -4(%ebp)

Pin会将cpuid, popf and REP prefixed 指令在执行break 成很多BBLs,导致执行的基本块比预想的要多。(主要原因是这些指令有隐式循环,所以Pin会将其拆分成多个BBLs)

Download

  1. Download the kit from Intel website
  2. Because the compatibility problem may you should install pin with archlinux package

Installation

This part is always needed by pintool, for example Zsim, Sniper.

When you meet the following situation, you should consider update your pin version even you can ignore this warning by use flags like -ifeellucky under high compatibility risk.

shaojiemike@snode6 ~/github/ramulator-pim/zsim-ramulator/pin  [08:05:47]
> ./pin
E: 5.4 is not a supported linux release

because this will easily lead to the problem

Pin app terminated abnormally due to signal 6. # or signal 4.

Install pintool(zsim) by reconfig pin version

  1. My first idea is try a compatible pin version (passd a simple test pintool, whatever) instead of the old pin.
  2. Find the suitable simpler pintool can reproduce the situation (old pin failed, but newest pin is passed)
    1. TODO: build(fix pin2.14 CXX_ABI compatibility bug), test suitability
  3. debug the pin tool in details (See in another blog)

插桩粒度

  • Trace instrumentation mode:以动态基本块BBLs为分析单位
  • Instruction instrumentation mode:以指令为分析单位
  • 其余粒度/角度,这些模式通过“缓存”插装请求来实现,因此会产生空间开销,这些模式也被称为预先插装
  • Image instrumentation mode: 通过前提知道需要执行的所有代码,能绘制出全局的插桩情况图
  • Routine instrumentation mode: 理解为函数级插桩,可以对目标程序所调用的函数进行遍历,并在每一个routine中对instruction做更细粒度的遍历。
  • 两者都是在IMG加载时提前插桩,所有不一定routine会被执行。

范围image, section, routine, trace, basic block, instruction的含义和运行时关系

  • image包含section,section包含routine(理由:SEC_Img()RTN_Sec(),后面的静态遍历IMG中指令条数的代码也能说明)
  • routine指的是程序中的函数或子程序,trace指的是程序执行过程中的一条路径,basic block指的是一段连续的指令序列,其中没有跳转指令,instruction指的是程序中的一条指令。
  • 在程序执行时,一个routine可以包含多个trace,一个trace可以包含多个basic block,一个basic block可以包含多个instruction。
for (SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec))
{
    for (RTN rtn = SEC_RtnHead(sec); RTN_Valid(rtn); rtn = RTN_Next(rtn))
    {
        // Prepare for processing of RTN, an  RTN is not broken up into BBLs,
        // it is merely a sequence of INSs
        RTN_Open(rtn);

        for (INS ins = RTN_InsHead(rtn); INS_Valid(ins); ins = INS_Next(ins))
        {
            count++;
        }

        // to preserve space, release data associated with RTN after we have processed it
        RTN_Close(rtn);
    }
}

API

最重要的是

  • 插桩机制(instrumentation code):插入位置,在什么位置插入什么样的代码
  • 分析代码(analysis code):插入内容,在插桩点执行的代码 (和上一点的区别是什么???)

插桩机制(instrumentation code)

  • TRACE_AddInstrumentFunction Add a function used to instrument at trace granularity
  • BBL粒度插桩相关API
  • INS_AddInstrumentFunction() Add a function used to instrument at instruction granularity
  • 指令粒度插桩相关API
  • IMG_AddInstrumentFunction() Use this to register a call back to catch the loading of an image
  • 插桩不仅可以对每个指令插桩,还可以通过分类筛选后,只对符合要求的指令进行插桩
  • 比如,使用INS_InsertPredicatedCall()

遍历所有的指令

// Forward pass over all instructions in bbl
for( INS ins= BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins) )

// Forward pass over all instructions in routine
for( INS ins= RTN_InsHead(rtn); INS_Valid(ins); ins = INS_Next(ins) )

遍历trace内BBLs

// Visit every basic block  in the trace
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
    // Insert a call to docount before every bbl, passing the number of instructions
    BBL_InsertCall(bbl, IPOINT_BEFORE, (AFUNPTR)docount, IARG_UINT32, BBL_NumIns(bbl), IARG_END);
}

遍历指令里的memOperands

UINT32 memOperands = INS_MemoryOperandCount(ins);

// Iterate over each memory operand of the instruction.
for (UINT32 memOp = 0; memOp < memOperands; memOp++){
    if (INS_MemoryOperandIsRead(ins, memOp)||INS_MemoryOperandIsWritten(ins, memOp)
        //xxx
}

Pintool

最重要的是

  • 插桩机制(instrumentation code):插入位置,在什么位置插入什么样的代码
  • 分析代码(analysis code):插入内容,在插桩点执行的代码 (和上一点的区别是什么???)

Pintool代码

示例分析

// IPOINT_BEFORE 时运行的分析函数
VOID printip(VOID* ip) { fprintf(trace, "%p\n", ip); }

// Pin calls this function every time a new instruction is encountered
VOID InstructionFuc(INS ins, VOID* v)
{
    // Insert a call to printip before every instruction, and pass it the IP
    // IARG_INST_PTR:指令地址 一类的全局变量???
    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)printip, IARG_INST_PTR, IARG_END);
}

int main(int argc, char* argv[])
{
    // Initialize pin
    if (PIN_Init(argc, argv)) return Usage();

    // 登记InstructionFuc为以指令粒度插桩时每条指令触发的函数
    INS_AddInstrumentFunction(InstructionFuc, 0);

    // 登记PrintFuc为程序结束时触发的函数
    PIN_AddFiniFunction(PrintFuc, 0);

    // 部署好触发机制后开始运行程序
    PIN_StartProgram();

    return 0;
}

Debug pintool

目标:以样例插桩工具的源码为对象,熟悉pin的debug流程。

官方教程为例子:

uname -a #intel64
cd source/tools/ManualExamples
# source/tools/Config/makefile.config list all make option
make all OPT=-O0 DEBUG=1 TARGET=intel64 |tee make.log|my_hl
# or just select one: make obj-intel64/inscount0.so
# $(OBJDIR)%$(PINTOOL_SUFFIX) - Default rule for building tools.
#    Example: make obj-intel64/mytool.so

测试运行

../../../pin -t obj-intel64/inscount0.so -- ./a.out #正常统计指令数 to inscount.out

下面介绍Pin 提供的debug工具:

首先创建所需的-gstack-debugger.so和应用fibonacci.exe

cd source/tools/ManualExamples
make OPT=-O0 DEBUG=1 stack-debugger.test

其中OPT=-O0选项来自官方文档Using Fast Call Linkages小节,说明需要OPT=-O0选项来屏蔽makefile中的-fomit-frame-pointer选项,使得GDB能正常显示stack trace(函数堆栈?)

Debug application in Pin JIT mode

$ ../../../pin -appdebug -t obj-intel64/stack-debugger.so -- obj-intel64/fibonacci.exe 1000
Application stopped until continued from debugger.
Start GDB, then issue this command at the prompt:
  target remote :33030

使用pin的-appdebug选项,在程序第一条指令前暂停,并启动debugger窗口。在另一个窗口里gdb通过pid连接:

$ gdb fibonacci #如果没指定应用obj-intel64/fibonacci.exe
(gdb) target remote :33030 #连接gdb端口
(gdb) file obj-intel64/fibonacci.exe #如果没指定应用, 需要指定程序来加载symbols
(gdb) b main #continue 等正常操作

Pintool添加自定义debug指令

能够在上一小节的debug窗口里,通过自定义debug指令打印自定义程序相关信息(比如当前stack使用大小)

Pintool设置满足条件时break并启动debug窗口

Pintool “stack-debugger” 能够监控每条分配stack空间的指令,并当stack使用达到阈值时stop at a breakpoint。

这功能由两部分代码实现,一个是插桩代码,一个是分析代码。

static VOID Instruction(INS ins, VOID *)
{
    if (!EnableInstrumentation) // ROI(Region of interest)开始插桩测量
        return;

    if (INS_RegWContain(ins, REG_STACK_PTR)) //判断指令是不是会改变stack指针(allocate stack)
    {
        IPOINT where = IPOINT_AFTER;
        if (!INS_IsValidForIpointAfter(ins))
            where = IPOINT_TAKEN_BRANCH; //寻找stack空间判断函数插入位置(指令执行完的位置)。如果不是after, 就是taken branch

        INS_InsertIfCall(ins, where, (AFUNPTR)OnStackChangeIf, IARG_REG_VALUE, REG_STACK_PTR,
            IARG_REG_VALUE, RegTinfo, IARG_END); // 插入OnStackChangeIf函数,如果OnStackChangeIf()返回non-zero, 执行下面的DoBreakpoint函数
        INS_InsertThenCall(ins, where, (AFUNPTR)DoBreakpoint, IARG_CONST_CONTEXT, IARG_THREAD_ID, IARG_END);
    }
}

所需的两个函数的分析代码如下:

static ADDRINT OnStackChangeIf(ADDRINT sp, ADDRINT addrInfo)
{
    TINFO *tinfo = reinterpret_cast<TINFO *>(addrInfo);

    // The stack pointer may go above the base slightly.  (For example, the application's dynamic
    // loader does this briefly during start-up.)
    //
    if (sp > tinfo->_stackBase)
        return 0;

    // Keep track of the maximum stack usage.
    //
    size_t size = tinfo->_stackBase - sp;
    if (size > tinfo->_max) 
        tinfo->_max = size; //更新stack使用大小

    // See if we need to trigger a breakpoint.
    //
    if (BreakOnNewMax && size > tinfo->_maxReported)
        return 1;
    if (BreakOnSize && size >= BreakOnSize)
        return 1;
    return 0;
}

static VOID DoBreakpoint(const CONTEXT *ctxt, THREADID tid)
{
    TINFO *tinfo = reinterpret_cast<TINFO *>(PIN_GetContextReg(ctxt, RegTinfo));

    // Keep track of the maximum reported stack usage for "stackbreak newmax".
    //
    size_t size = tinfo->_stackBase - PIN_GetContextReg(ctxt, REG_STACK_PTR);
    if (size > tinfo->_maxReported)
        tinfo->_maxReported = size;

    ConnectDebugger();  // Ask the user to connect a debugger, if it is not already connected.

    // Construct a string that the debugger will print when it stops.  If a debugger is
    // not connected, no breakpoint is triggered and execution resumes immediately.
    //
    tinfo->_os.str("");
    tinfo->_os << "Thread " << std::dec << tid << " uses " << size << " bytes of stack.";
    PIN_ApplicationBreakpoint(ctxt, tid, FALSE, tinfo->_os.str());
}

OnStackChangeIf函数监控当前的stack使用并判断是否到达阈值。DoBreakpoint函数连接debugger窗口,然后触发breakpoint,并打印相关信息。

也可以使用-appdebug_enable参数,取消在第一条指令前开启GDB窗口的功能,而是在触发如上代码的break时,才开启GDB窗口的连接。

而上述代码中的ConnectDebugger函数实现如下:

static void ConnectDebugger()
{
    if (PIN_GetDebugStatus() != DEBUG_STATUS_UNCONNECTED) //判断是不是已有debugger连接
        return;

    DEBUG_CONNECTION_INFO info;
    if (!PIN_GetDebugConnectionInfo(&info) || info._type != DEBUG_CONNECTION_TYPE_TCP_SERVER) //PIN_GetDebugConnectionInfo()获取GDB所需的tcp连接端口
        return;

    *Output << "Triggered stack-limit breakpoint.\n";
    *Output << "Start GDB and enter this command:\n";
    *Output << "  target remote :" << std::dec << info._tcpServer._tcpPort << "\n";
    *Output << std::flush;

    if (PIN_WaitForDebuggerToConnect(1000*KnobTimeout.Value())) //等待其余GDB窗口的连接
        return;

    *Output << "No debugger attached after " << KnobTimeout.Value() << " seconds.\n";
    *Output << "Resuming application without stopping.\n";
    *Output << std::flush;
}

Tips for Debugging a Pintool

这部分讲述了如何debug Pintool中的问题。(对Pintool的原理也能更了解

为此,pin使用了-pause_tool n 暂停n秒等待gdb连接。

../../../pin -pause_tool 10 -t /staff/shaojiemike/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples/obj-intel64/stack-debugger.so -- obj-intel64/fibonacci.exe 1000
Pausing for 10 seconds to attach to process with pid 3502000
To load the debug info to gdb use:
*****************************************************************
set sysroot /not/existing/dir
file
add-symbol-file /staff/shaojiemike/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples/obj-intel64/stack-debugger.so 0x7f3105f24170 -s .data 0x7f31061288a0 -s .bss 0x7f3106129280
*****************************************************************

注意gdb对象既不是pin也不是stack-debugger.so,而是intel64/bin/pinbin。原因是intel64/bin/pinbinpin执行时的核心程序,通过htop监控可以看出。

# shaojiemike @ snode6 in ~/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples on git:dev x [19:57:26]
$ gdb ../../../intel64/bin/pinbin 
(gdb) attach 3502000

这时GDB缺少了stack-debugger.so的调试信息,需要手动添加。这里的add-symbol-file命令是在pin启动时打印出来的,直接复制粘贴即可。

(gdb) add-symbol-file /staff/shaojiemike/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples/obj-intel64/stack-debugger.so 0x7f3105f24170 -s .data 0x7f31061288a0 -s .bss 0x7f3106129280
(gdb) b main #或者 b stack-debugger.cpp:94
gef➤  info b                                                         
Num     Type           Disp Enb Address            What                    
1       breakpoint     keep y   <MULTIPLE>                                  
1.1                         y   0x00000000000f4460 <main>          # 无法访问的地址,需要去除         
1.2                         y   0x00007f3105f36b65 in main(int, char**) at stack-debugger.cpp:94 
(gdb) del 1.1
(gdb) c

个人尝试: 使用VSCODE调试Pintool

  • 想法:VSCODE的GDB也可以attach PID,理论上是可以的
  • 实际问题:VSCODE attach pid后,不会stopAtEntry,只会在已经设置好的断点暂停。但是无法访问到stack-debugger.so的调试信息,无法设置断点。

构建Pintool

  • 首先需要熟悉API
  • PinTool 编译需要自身的 Pin CRT(C RunTime)库,这个库是 Pin 提供的,可以在 Pin 安装目录下找到。

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://paper.seebug.org/1742/

https://software.intel.com/sites/landingpage/pintool/docs/98690/Pin/doc/html/index.html#APPDEBUG_UNIX

Zsim

简介

Zsim模拟器是

  • 一个快速的x86-64多核模拟器,它利用英特尔Pin工具包收集进程的内存访问跟踪,并在zsim模拟器中重放跟踪。
  • 一种基于 PIN 的二进制插桩工具,可以在运行时对指令进行插桩,并插入自定义的函数调用,用于模拟内存子系统的行为。
  • 它最初是为了评估ZCache而编写的,但后来它扩展了其功能
  • zsim的主要目标是快速,简单和准确,重点是模拟内存层次结构和大型异构系统

Colorful Commands

导言

电脑玩家经常说RGB是最重要的,对于程序员来说,彩色的terminal有助于快速的分辨输出的有效信息。为此有一些有意思的彩色输出命令。

还有些必备的

  1. 内网下的文件同步: https://github.com/syncthing/syncthing

Cheat sheet (命令行小抄/备忘录)

最经典 tdlr

使用时需要网络

npm install -g tldr     
pip3 install tldr     
tealdeer - simplest example

a RUST fast version oftdlr

Tips: OpenSSL development headers get a "failed to run custom build command for openssl-sys" error message. The package is calledlibssl-dev on Ubuntu.

# install
cargo install tealdeer

# 使用
tdlr <>

部分支持中文,支持多平台

自定义
  1. cheat + 编写的cheatsheets
    1. 支持fzf和自动补全
  2. kb. A minimalist knowledge base manager
  3. eg provides examples of common uses of command line tools.
支持在线网页版
  1. Linux Command Line Cheat Sheet
  2. devhints.io
  3. 各种的选项,不止命令,包括bash, vim, mysql, git
  4. 语言 go, python, java, JS, NodeJS, Ruby 3.
  5. navi
  6. 支持语意转换的补全 🔥
  7. cheat.sh 🔥
  8. 支持 curl命令直接访问或者交互式
  9. 支持补全
  10. 返回内容集成cheat cheat.sheets tdlr
  11. bropages.org
  12. 用户自发投票排序的命令用例
navi installation & usage
# install
cargo install --locked navi

# download default cheatsheet
navi repo add denisidoro/cheats 

# 使用
navi
# 基于fzf寻找需要指令

SHELL

awesome-shell里多看看。

set_tsj.sh

HOME=/home/t00906153
export SHELL=zsh
export HOME=$HOME
export HISTFILE=$HOME/.zsh_history
cd
zsh

oh my zsh : hyq version

虽然这个ohmyzsh好用,但是我用惯了hyq的模板, 从github上下载后解压就安装了zsh模板。(之后可以考虑传到云盘或者cloudflare)

HOME=/home/xxx
export SHELL=zsh
export PATH=$HOME/.local/bin:$PATH
export HISTFILE=$HOME/.zsh_history
zsh
# git clone https://github.com/Kirrito-k423/QuickStartLinux.git --depth=1
# cd QuickStartLinux/resources
wget https://raw.githubusercontent.com/Kirrito-k423/QuickStartLinux/main/resources/zsh.tar
tar xvf zsh.tar -C $HOME
oh my zsh : install from src

有时候hyq模版有些兼容性问题,比较老的zsh不支持(大约是5.0.2到5.5.1的版本)。这时候只能手动安装了,包括主题和插件。请看zsh一文。

zsh_history 支持多端口同步,实时保存

~/.zshrc
HISTFILE=~/.zsh_history
SAVEHIST=30000 # 最多 10000 条命令的历史记录
setopt APPEND_HISTORY #  退出 zsh 会话时,命令不会被覆盖式地写入到历史文件,而是追加到该文件的末尾
setopt INC_APPEND_HISTORY # 会话进行中也会将命令追加到历史文件中
setopt SHARE_HISTORY # 所有会话中输入的命令保存到一个共享的历史列表中
export HIST_SAVE_FREQ=10  # 每10次命令保存一次
ulimit -c unlimited
echo '$HOME/core-%e.%p.%h.%t' > /proc/sys/kernel/core_pattern
cd ~

窗口管理器 tmux-like

大部分Linux 系统自带的 screen 命令来多终端控制

外部控制:

  • 创建: screen -S name
  • 恢复: screen -r name
  • 列表: screen -ls
  • 删除: screen -X -S name kill

内部控制:

  • CtrlA + d 分离
  • CtrlA + x 终端上锁,CtrlA + k 是kill
  • CtrlA + S 上下分屏,CtrlA + | 左右分屏。
    • screen -S name创建会话后,需要在新窗口中执行screen来创建额外的终端;这样分屏之后才有两个以上的终端可以使用。
    • CtrlA + w 查看终端
    • CtrlA + [数字] 切换到第几个
    • CtrlA + Tab 返回上一个
    • CtrlA + X 关闭分屏
    • CtrlA + : 进入命令模式,输入resize -v 100或者resize -h 100调整大小。
oh my tmux 🔥
## Install
cd
git clone https://github.com/gpakosz/.tmux.git
ln -s -f .tmux/.tmux.conf
cp .tmux/.tmux.conf.local .

# or
cd ~/resources
# wget https://gitee.com/shaojiemike/oh-my-tmux/repository/blazearchive/master.zip?Expires=1629202041&Signature=Iiolnv2jN6GZM0hBWY09QZAYYPizWCutAMAkhd%2Bwp%2Fo%3D
unzip  oh-my-tmux-master.zip -d ~/
ln -s -f ~/oh-my-tmux-master/.tmux.conf ~/.tmux.conf
cp ~/oh-my-tmux-master/.tmux.conf.local ~/.tmux.conf.local
基于Rust的zellij

开发编辑器 vim-like

vimrc 🔥
git clone --depth=1 https://github.com/amix/vimrc.git ~/.vim_runtime
sh ~/.vim_runtime/install_awesome_vimrc.sh
emacs

针对不同语言有许多可选插件

### Ubuntu install emacs27

add-apt-repository ppa:kelleyk/emacs
apt-get update
apt-get install emacs27

#### 问题:

dpkg-deb: error: paste subprocess was killed by signal (Broken pipe)
Errors were encountered while processing:
/var/cache/apt/archives/emacs27-common_27.1~1.git86d8d76aa3-kk2+20.04_all.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)

版本解决,强制安装 sudo apt-get -o Dpkg::Options::="--force-overwrite" install emacs27-common

sudo apt --purge remove emacs27
sudo apt --purge remove emacs
sudo apt --purge remove emacs-common
sudo apt --fix-broken install
sudo apt autoremove
sudo apt install emacs27
emacs --version
doom

### install doom

git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.emacs.d
~/.emacs.d/bin/doom install

中文教程 https://www.bilibili.com/read/cv11371146

常用命令的"Colorful"版本

cd

z.lua - learning cd 🔥

Install LUA

curl -R -O http://www.lua.org/ftp/lua-5.4.4.tar.gz       
tar zxf lua-5.4.4.tar.gz       
cd lua-5.4.4       
make all test       
sudo make install # usr/bin       

Install

cd ~/github
git clone https://github.com/skywind3000/z.lua.git

# vim ~/.zshrc
alias zz='z -c' # 严格匹配当前路径的子路径
alias zi='z -i' # 使用交互式选择模式
alias zf='z -I' # 使用 fzf 对多个结果进行选择
alias zb='z -b' # 快速回到父目录
#eval "$(lua /path/to/z.lua  --init zsh)"    # ZSH 初始化
eval "$(lua ~/github/z.lua/z.lua  --init zsh)"

常用命令

# 弹出栈顶 (cd 到上一次的老路径),和 "z -0" 相同
$ z -

# 显示当前的 dir stack
$ z --

# 交互式
z -i foo    # 进入交互式选择模式,让你自己挑选去哪里(多个结果的话)
z -I foo    # 进入交互式选择模式,但是使用 fzf 来选择

# 匹配
z foo$
z foo       # 跳转到包含 foo 并且权重(Frecent)最高的路径       
z foo bar   # 跳转到同时包含 foo 和 bar 并且权重最高的路径       
z -r foo    # 跳转到包含 foo 并且访问次数最高的路径       
z -t foo    # 跳转到包含 foo 并且最近访问过的路径       
z -l foo    # 不跳转,只是列出所有匹配 foo 的路径       
z -c foo    # 跳转到包含 foo 并且是当前路径的子路径的权重最高的路径       
z -e foo    # 不跳转,只是打印出匹配 foo 并且权重最高的路径       
z -i foo    # 进入交互式选择模式,让你自己挑选去哪里(多个结果的话)       
z -I foo    # 进入交互式选择模式,但是使用 fzf 来选择       
z -b foo    # 跳转到父目录中名称以 foo 开头的那一级       

缺点:

  • 没去过的路径,每级文件夹的补全没有了
  • 可以和cd结合使用

ls

exa 🔥 - better ls
# Manual installation from GitHub. Ubuntu 20.10才支持
wget https://github.com/ogham/exa/releases/download/v0.10.1/exa-linux-x86_64-musl-v0.10.1.zip
unzip exa-linux-x86_64-musl-v0.10.1.zip
mv bin/exa ~/.local/bin

# 使用
exa -l
# 文件夹大小
du -d 1 -h .

grep

rg (Fast & Good multi-platform compatibility) > ag > ack(ack-grep) 🔥

repgrep(rg) Rust编写
# ripgrep(rg) 但是readme说这样有bugs       
sudo apt-get install ripgrep       
# 可执行文件 (推荐)    
wget https://github.com/BurntSushi/ripgrep/releases/download/13.0.0/ripgrep-13.0.0-x86_64-unknown-linux-musl.tar.gz      
tar -zxvf ripgrep-13.0.0-x86_64-unknown-linux-musl.tar.gz       
mv ./ripgrep-13.0.0-x86_64-unknown-linux-musl/rg ~/.local/bin       

repgrep(rg) 常用选项

  • --no-ignore 忽略.gitignore之类的文件,也搜索忽略的文件。(默认是不搜索的)
  • -t txt 指定搜索类型
  • rg 'content' ABC/*.cpp搜索和正则ABC/*.cpp匹配的文件
ag
# ag 2020年就不维护了       
apt-get install silversearcher-ag       
# It ignores file patterns from your .gitignore and .hgignore.       
# use -u or -U option to reinclude these files to search scoop       

find

find . -name "*xxx*"

fzf 🔥带预览的find
# ubuntu
sudo apt install fzf

# GIT install
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
source ~/.zshrc
# Vim-plugin
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }

# 使用
fzf --preview 'less {}'

# 安装了bat
fzf --preview "batcat --style=numbers --color=always --line-range :500 {}"
telescope.nvim 也带预览的find

官网

# 先安装vim-plug
curl -fLo~/.vim/autoload/plug.vim --create-dirs \ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
# 修改~/.vimrc
call plug#begin()
Plug 'nvim-lua/plenary.nvim'
Plug 'nvim-telescope/telescope.nvim'
call plug#end()

# 还需要 Neovim
to do
fdfind(fd)

A simple, fast and user-friendly alternative to 'find'

cat

bat 🔥 - colorful cat
# Install
sudo apt install bat

# 使用
batcat filename
# 指定行号
alias cat="batcat"
cat -r 35:42 /etc/hosts

git

gitui 🔥 - fast Rust lazygit

https://github.com/extrawurst/gitui/releases

# 建议Rust,三句命令,安装Rust,source,gitui
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
cargo install gitui

# 安装(由于还在开发中建议去官网 , 现在不支持armV7
wget https://github.com/extrawurst/gitui/releases/download/v0.20.1/gitui-linux-musl.tar.gz
tar -zxvf gitui-linux-musl.tar.gz
mv gitui ~/.local/bin
lazygit
# install go
wget https://go.dev/dl/go1.18.1.linux-amd64.tar.gz

git clone https://github.com/jesseduffield/lazygit.git
cd lazygit
go install

docker

lazydocker

高亮终端输出/log文件

bash脚本输出颜色文本示例

RED='\033[0;31m'       
NC='\033[0m' 
# No Color       
printf "I ${RED}love${NC} Stack Overflow\n"       
echo -e "\033[5;36m Orz 旧容器(镜像)已清理\033[0m"       

颜色编号如下

颜色 编号
Black 0;30
Dark Gray 1;30
Red 0;31
Light Red 1;31
Green 0;32
Light Green 1;32
Brown/Orange 0;33
Yellow 1;33
Blue 0;34
Light Blue 1;34
Purple 0;35
Light Purple 1;35
Cyan 0;36
Blue 0;37
Light Cyan 1;36
Light Gray 0;37
White 1;37
hl 🔥自定义高亮各种log文件

通过regular expressions自定义高亮各种log文件

install需要 lex

git clone https://github.com/mbornet-hl/hl       
make clean; 
make      
cp hl /usr/local/bin 
# move       

颜色支持(3浅中深 * 6颜色 * 背景色反转)

# 前面 123 是深浅 , 4是下划线       
# 字母大写是背景色反转       
-r : red        -g : green        -y : yellow        -b : blue        -m : magenta        -c : cyan        -w : white        

正则标记log关键词

绿色和红色

cat exemple.log | hl -g start -r stop       

正则表示

-e : extended regular expressions
-i : ignore case

hl -ei -g '(start(|ing|ed))' -r '(stop(|ping|ped))'

## ip 匹配
curl --trace-ascii - www.baidu.com|hl -ei -1R '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'

命令配置文件 hl_ha.cfg

默认设置

export HL_CONF=/staff/shaojiemike/github/hl/config_files       
echo $HL_CONF       │/staff/shaojiemike/github/hl/config_files       
-%c : specifies the beginning of a range colorized in color 'c'       
-.  : specifies the end of the previous range       

Colorize hl configurations :

hl -vop '*' | hl --hl_conf       

example Commands

lD # ls by date       
lW # ls by week       
ifconfig -a | hl --ifconfig       
# ping tcpdump fdisk apt-get diff       
# ip ibstat iptables passwd       

errors

常用方式

~/.zshrc 里如下配置:

export HL_CONF=/home/shaojiemike/github/hl/config_files       
function my_hl(){          hl -eg '\$\{?[A-Z_]+\}?' -ec ' ([A-Z_-]+) ' -eic '(nothing|note)' -eiy ' (-([a-z_-]+))' -eiy '0x[0-9a-z]+' --errors -eig '(yes)' -eir '((^| )no($| ))|(none)|(not)|(null)|(please)|( id )' -ir error -ir wrong -ib '(line)|(file)' -eiy '(warn(|ing))|(wait)|(idle)|(skip)' -im return -ic '(checking)' -eiy ' (__(.*)__) ' -ei1W '((\w*/[.A-Za-z0-9_/-]*[A-Za-z0-9_/-]*)("|$)?)|((\w*/[.A-Za-z0-9_/-]*[A-Za-z0-9_/-]*)(")? ) ' -3B '[0-9][0-9.]+' -3B ' ([0-9])|(#[0-9]+)' -eig '(start(|ing))' -eir '(end(|ing))'       }       
alias ifconfig='ifconfig | hl --ifconfig'       
alias ip='ip a|hl --ip '       
alias df='df -h |hl --df'       
alias ibstat='ibstat |hl --ibstat'       

编译时如此使用make 2>&1|my_hl

系统信息

dua-cli - best disk space viewer 🔥

more

资源监控

资源监控软件netdata
  • netdata 默认挂载在http://127.0.0.1:19999/。想要WebUI运行 sudo netdata -i node5.acsalab.com
  • cpu, disk, memory, network,温度都有记录
  • arm下有问题,需要自己编译

资源监控命令bottom(htop like)
# install
curl -LO https://github.com/ClementTsang/bottom/releases/download/0.6.8/bottom_0.6.8_amd64.deb
sudo dpkg -i bottom_0.6.8_amd64.deb

# 使用
btm

类似s-tui可以观察CPU 温度,频率

网络监控 bmon

bmon是类 Unix 系统中一个基于文本,简单但非常强大的网络监视和调试工具

Compile yourself

Install libconfuse

sh wget https://github.com/martinh/libconfuse/releases/download/v2.8/confuse-2.8.zip unzip confuse-2.8.zip && cd confuse-2.8 PATH=/usr/local/opt/gettext/bin:$PATH ./configure make make install Install bmon

sh git clone https://github.com/tgraf/bmon.git cd bmon ./autogen.sh ./configure make make install bmon

文件管理器

nnn 多平台

https://github.com/jarun/nnn#quickstart

很复杂,插件和快捷键超级多

sh # 版本很低 3.0 sudo apt-get install nnn # Q 退出

ranger 基于vi的支持预览的横向多级显示 🔥

https://github.com/ranger/ranger

pip install ranger-fm

# renger直接使用,方向键或者hjkl,可以直接跳转到vim修改
xplr - 筛选排序tips板 - 支持多选,正则查找, mov改名delete 🔥

https://github.com/sayanarijit/xplr

sh cargo install --locked --force xplr

参考文献

[C++ Basic] Grammar

概要

C++ 基础知识和语法,包括C++11,C++17,C++23的各种语言支持。

C++编程语言历史 和 设计思路

在纷繁多变的世界里茁壮成长:C++ 2006–2020

C 与 C++、java 的 区别

支持范式模板编程 (generic programming)

  1. 模板代码(增加泛型编程能力,类似python),
  2. 泛型编程是一种以通用性为中心的编程范式。在泛型编程中,程序通过使用参数化类型(或称为模板)来实现数据类型无关的算法和数据结构
  3. 强⼤的 Standard Template Library (STL) 标准库, 也是基于泛型编程的产物。
  4. 包括:容器、迭代器、算法和函数对象 四类
  5. 元编程(e.g., constexpr )编译时推导出变量是否为固定常数。
  6. 一些语法和关键字,增加了 new 和 delete,auto

支持面向对象编程 (object-oriented programming) 的拓展

  • 类和对象:C++允许定义类和创建对象。类是一种用户自定义的数据类型,可以包含成员变量和成员函数。对象是类的一个实例,可以通过类来创建多个对象。C语言中没有类和对象的概念,只能使用结构体和函数来组织数据和行为。
  • 封装:C++支持封装,可以将数据和相关的操作封装在一个类中,并使用访问修饰符来控制对类成员的访问权限。C语言没有封装的概念,所有的数据和函数都是公开的。
  • 继承:C++支持继承,允许创建派生类从基类继承属性和行为。继承可以实现代码重用和类的层次化。C语言没有继承的概念。
  • 多态:C++支持多态,允许通过基类指针或引用来调用派生类的虚函数,实现动态绑定和运行时多态性。C语言没有多态的概念。
  • 异常处理:C++提供异常处理机制,可以通过抛出和捕获异常来处理程序中的错误和异常情况。C语言没有内置的异常处理机制。
C++ 与 java 的区别
  1. 内存管理:C++中的内存管理是手动的,程序员需要显式地分配和释放内存。C++提供了new和delete关键字来进行动态内存分配和释放。Java中的内存管理是自动的,使用垃圾回收机制来自动管理内存,程序员不需要手动释放内存。
  2. 指针:C++支持指针操作,允许直接访问和修改内存地址。Java中没有指针的概念,所有的数据访问都是通过引用进行的。
  3. 运行环境:C++是一种编译型语言,源代码在编译后被转换为机器码,并直接在操作系统上运行。Java是一种解释型语言,源代码在编译后生成字节码,然后由Java虚拟机(JVM)解释执行。
  4. 平台依赖性:C++代码在不同的平台上需要重新编译,因为它直接与底层系统交互。Java代码是平台无关的,一次编译的字节码可以在任何支持Java虚拟机的平台上运行。

  5. C++更适合系统级编程、游戏开发等需要更高的性能和底层控制的场景。

  6. Java更适合企业级应用开发、网络编程等需要跨平台和可移植性的场景。

基础知识与坑

程序执行入口

The default program entry function is main, but can be changed in two situations:

  1. use stupid #define xxx main in header file to replace the name which maybe ignored by silly search bar in VSCODE.
  2. use -exxx compile flag

语句末尾的分号

  • 在 C++ 中,是否需要在语句的末尾使用分号(;)取决于上下文。
  • C++ 语法的基本规则是:分号用来标识一条语句的结束,而有些结构并不是严格的语句,因此不需要分号。

  • 声明(变量、类、结构体、枚举、函数原型、类型别名):都需要分号作为结束。

  • 函数定义控制语句(如 if, while, for)、复合语句{}) 不需要分号。
  • 预处理指令(如 #define, #include):不需要分号,因为它们不是 C++ 语法层面的内容。
  • 作用域结束的 } 不需要分号,但声明类或结构体时 } 后要加分号。

为什么类/结构体需要分号

C++ 中的类和结构体定义实际上是一种声明,它们的定义是一种复杂的声明语句,因此必须用分号来结束它们。

总结来说,分号用来结束语句,包括声明、表达式和执行体等,但当你定义一个复合结构(如函数定义、控制语句)时,不需要分号来结束复合结构的定义。

逗号运算符(Comma)

重名变量的优先级

int getKthAncestor(int node, int k) {
    int node= getKthAncestor(saved[node],--k);  
    return node;
}
 //为什么第二行的node会提前被修改为0,导致传入函数getKthAncestor的saved[node]的node值为0
 //如下去掉int,也不会错。因为int node 会初始化node为0
 int getKthAncestor(int node, int k) {
    node= getKthAncestor(saved[node],--k);  
    return node;
}

根据C++的作用域规则,内层的局部变量会覆盖外层的同名变量。因此,在第二行的语句中,node引用的是函数参数中的node,而不是你想要的之前定义的node。

为了避免这个问题,你可以修改代码,避免重复定义变量名。例如,可以将第二行的变量名改为newNode或其他不同的名称,以避免与函数参数名冲突。

运算符优先级

运算符性质:

  • 接受的操作数,
  • 优先级,
    • 特殊:逻辑和(&&)先于逻辑或(||)、四则运算先于位运算
    • 位运算优先级低于判断符号,记得写括号。
    • 赋值(=)优先级最低
  • 结合性,
    • 左结合性: 大部分运算(加减乘除)
    • 右结合性:赋值运算符。程序会先计算它右边的表达式的值,然后再计算它左边的表达式的值
  • 返回值
    • 赋值运算符的返回值是赋值后左操作数的引用

变量类型以及Macro constants

https://en.cppreference.com/w/cpp/language/types

https://en.cppreference.com/w/cpp/types/integer

//返回与平台相关的数值类型的极值
std::numeric_limits<double>::max()
std::numeric_limits<int>::min()

#include<limits.h>
INT_MAX
FLT_MAX (or DBL_MAX ) 
-FLT_MAX (or -DBL_MAX ) 

关键词

extern 
const
constexpr //C++11引入的关键字,用于编译时的常量与常量函数。
volatile    //是指每次需要引用某个变量的数据时,都必须从内存原地址读取,而不是编译器优化后寄存器间接读取.(必须写回内存,为了多进程并发而设计的。)
inline 

static 关键字

static 作⽤:控制变量的存储⽅式和作用范围(可⻅性)。

  1. 修饰局部变量
    • 存放位置:栈区 -> 静态数据区(data段或者bss段)
    • 生命周期:程序结束才会释放
    • 作用域:还是局部代码块
  2. 修饰函数与全局变量
    • 使其作用范围由全工程文件可见变成了本文件可见

避免

  • 静态变量写到头文件会导致每个引用会有一份;

C++17 以后 局部的const static变量的初始化不是代码运行到才初始化,而是和全局static变量一样,在程序开始执行时初始化。

多文件共用static变量,需要添加 extern 关键字, 去掉static关键字

在 C++ 中,如果你希望 static 变量可以在多个文件中访问,直接写在头文件中是 不正确的,因为每个包含该头文件的源文件都会生成自己的独立 static 变量,导致它们互相独立,无法共享状态。

正确的解决方案

  1. 如果需要共享(全局变量的方式) 你应该将 encounteredAclops 声明为 extern 变量,并将其定义在一个 .cpp 文件中。

头文件:globals.h

#ifndef GLOBALS_H
#define GLOBALS_H

extern bool encounteredAclops;

#endif // GLOBALS_H

源文件:globals.cpp

#include "globals.h"

bool encounteredAclops = false;

在其他源文件中使用:

#include "globals.h"

void someFunction() {
    if (!encounteredAclops) {
        // Do something
        encounteredAclops = true;
    }
}

通过 extern,所有引用 encounteredAclops 的源文件都会共享同一个变量。


  1. 如果每个文件需要独立的变量

如果每个源文件都需要独立的 encounteredAclops,你可以将 static bool encounteredAclops = false; 放在各自的源文件中,而不需要放在头文件中。这是因为 static 的作用域仅限于当前编译单元。

每个源文件:

static bool encounteredAclops = false;

void someFunction() {
    if (!encounteredAclops) {
        // Do something
        encounteredAclops = true;
    }
}


  1. 如果需要在类中管理(推荐做法) 可以考虑将 encounteredAclops 作为一个类的静态成员变量来实现共享状态。

头文件:AclopsManager.h

#ifndef ACLOPS_MANAGER_H
#define ACLOPS_MANAGER_H

class AclopsManager {
public:
    static bool encounteredAclops;
};

#endif // ACLOPS_MANAGER_H

源文件:AclopsManager.cpp

#include "AclopsManager.h"

bool AclopsManager::encounteredAclops = false;

在其他源文件中使用:

#include "AclopsManager.h"

void someFunction() {
    if (!AclopsManager::encounteredAclops) {
        // Do something
        AclopsManager::encounteredAclops = true;
    }
}

这种方式既可以共享变量,又能保持代码组织清晰。


结论 - 如果需要共享变量:使用 extern 或类的静态成员变量。 - 如果需要独立变量:将 static 声明放在各自的源文件中。 - 不要直接在头文件中定义 static bool encounteredAclops,否则会导致每个包含头文件的源文件都生成自己的副本,违背初衷。

  1. 修饰类内函数
    • 静态成员函数:使用"static"修饰的成员函数称为静态成员函数。静态成员函数与类的对象无关,可以在没有创建对象的情况下直接通过类名调用。这意味着它们不需要通过类的对象来访问,而是属于整个类的。举例
    • 静态成员函数没有隐式的this指针,因此不能直接访问非静态成员变量和非静态成员函数。静态成员函数可以访问类的静态成员变量和其他静态成员函数。
    • static 成员函数不能被 virtual 修饰, static 成员不属于任何对象或实例,所以加上 virtual没有任何实际意义;
      • 静态成员函数没有 this 指针,虚函数的实现是为每⼀个对象分配⼀个vptr 指针,⽽ vptr 是通过 this 指针调⽤的,所以不能为 virtual;虚函数的调⽤关系,this->vptr->ctable->virtual function。
  2. 修饰类内的变量
    • 存放位置:栈区 -> 静态数据区(data段或者bss段)
    • 生命周期:程序结束才会释放
    • 意味着下一次调用函数时,静态局部变量将保持上一次调用时的值。
    • 由于不再属于某个类对象,可以直接通过类名初始化 int MyClass::staticVariable = 10;

多线程场景,修饰局部变量,会导致多线程共用,建议使用thread_local来避免竞争

static 局部变量在第一次被访问时初始化,且初始化过程是线程不安全的。如果两个线程几乎同时首次访问这个变量,可能会导致初始化竞争,进而引发未定义行为。

static 修饰初始化命令,只会执行一次,无论是否多次经过

问题场景:

static auto thread_core_map = GetCpuAffinityMap(device_id); 即使程序多次调用函数经过这行,但是这行命令也只会执行第一次。但是如果我把static关键词去除,就正常执行多次了。

当在一行代码中使用了 static 关键字时,变量的初始化只会在它第一次被执行时进行,之后即使多次经过这行代码,初始化的代码块也不会被重复执行

在例子中,thread_core_map 是一个静态局部变量,它在第一次经过时会调用 GetCpuAffinityMap(device_id) 函数并保存结果。在后续的函数调用中,即使再次经过这行代码,GetCpuAffinityMap(device_id) 不会被重新调用,因为 thread_core_map 已经被初始化过了。

去除 static 后,thread_core_map 会在每次经过这行代码时重新初始化,也就是每次都会调用 GetCpuAffinityMap(device_id)

解决方法:

如果你需要这行代码每次执行时都重新调用 GetCpuAffinityMap(device_id),那么应该去掉 static 关键字,或根据不同条件进行显式地重新初始化静态变量。

比如,可以这样实现惰性初始化或重置的功能:

static auto thread_core_map = GetCpuAffinityMap(device_id);
// 根据条件重新初始化
if (/* 条件 */) {
    thread_core_map = GetCpuAffinityMap(device_id);
}

这样你就可以在特定条件下让 thread_core_map 被重新赋值。

如果你对静态变量的初始化行为没有问题,但是希望特定场景下重新执行初始化函数,可以根据场景调整条件逻辑。

静态成员函数
#include <iostream>

class MyClass {
public:
    static void staticFunction() {
        std::cout << "This is a static member function." << std::endl;
    }
};

int main() {
    MyClass::staticFunction(); // 直接通过类名调用静态成员函数
    return 0;
}

const 关键字

当const修饰基本数据类型时,可以将其放置在类型说明符的前面或后面,效果是一样的。const关键字用于声明一个常量,即其值在声明后不可修改。

const int constantValue1 = 10; // const在类型说明符前
int const constantValue2 = 20; // const在类型说明符后

当const关键字位于指针变量或引用变量的左侧时,它用于修饰指针所指向的变量,即指针指向的内容为常量。当const关键字位于指针变量或引用变量的右侧时,它用于修饰指针或引用本身,即指针或引用本身是常量。

  1. 修饰指针指向的变量, 它指向的值不能修改:

    int x = 5;
    const int* ptr = &x;  // 指向常量整数的指针
    // *ptr = 10;        // 错误:不能通过const指针修改值
    x = 10;               // 合法:可以修改变量本身的值
    
  2. 修饰指针本身 ,它不能再指向别的变量,但指向(变量)的值可以修改。:

    const int y = 10;
    int* const ptr = &y;  // 常量指针指向整数
    // ptr = &x;         // 错误:不能修改指针本身
    // *ptr = 5;         // 合法:可以修改常量变量的值
    
  3. const int *const p3; //指向整形常量 的 常量指针 。它既不能再指向别的常量,指向的值也不能修改。

explicit

在C++, explicit 是一个关键字,用于修饰单参数构造函数,用于禁止隐式类型转换。

当一个构造函数被声明为 explicit 时,它指示编译器在使用该构造函数进行类型转换时只能使用显式调用,而不允许隐式的类型转换发生。

通过使用 explicit 关键字,可以防止一些意外的类型转换,提高代码的清晰性和安全性。它通常用于防止不必要的类型转换,特别是在单参数构造函数可能引起歧义或产生意外结果的情况下。

preprocessor directive

  • #include_next作用是 在寻找头文件时的头文件搜索优先级里,去除该文件所在的当前目录,主要是为C++头文件的重名问题提供一种解决方案。
    • 正确的用法:代码b.cpp想使用 自己拓展修改的stdlib.h, 那么在代码的目录下创建stdlib.h,并在该文件里#include_next "stdlib.h" 防止递归引用。

define、 const、 typedef、 inline

  • define:
    • define是一个预处理器指令,用于创建宏定义。它在编译之前对源代码进行简单的文本替换。可以用来定义常量、函数宏和条件编译等。
    • 优势:灵活性上占优,特别是在需要获取文件名、行号控制编译(不同平台编译)时行为的场景
    • 缺点:宏的调试和排错难度相对较高,因为宏的展开发生在编译前,出错时通常不容易直接定位到宏展开的具体代码。相比函数,宏的类型检查不严格,容易导致隐含的错误
    • 例如:#define PI 3.14159,在代码中将PI替换为3.14159。
# 是 字符串化操作符(Stringizing operator)

在 C/C++ 宏中,#字符串化操作符(Stringizing operator),它的作用是将宏参数转换为字符串文字(string literal)。

当在宏中使用 #key 时,key 被转换为一个字符串文字,即在代码中实际变为 "key"(包括引号)。如果不加 #key 将直接作为标记被使用,不会转为字符串。


示例:

以下是一个宏的例子,演示了 # 的作用:

宏定义:

#define TO_STRING(x) #x
#define CONCAT_AND_PRINT(a, b) printf("Concatenation: %s\n", TO_STRING(a##b));

宏使用:

int main() {
    int HelloWorld = 42;

    // 使用字符串化操作
    printf("%s\n", TO_STRING(HelloWorld)); // 输出: HelloWorld

    // 使用标记粘贴操作和字符串化操作
    CONCAT_AND_PRINT(Hello, World);        // 输出: Concatenation: HelloWorld
}

宏展开与作用:

  1. TO_STRING(HelloWorld)
    TO_STRING 将参数 HelloWorld 转换为字符串文字,展开为:

    printf("%s\n", "HelloWorld");
    
    输出:HelloWorld

  2. CONCAT_AND_PRINT(Hello, World)

  3. a##bHelloWorld 拼接成 HelloWorld
  4. TO_STRING(a##b) 将拼接后的标识符 HelloWorld 转换为字符串 "HelloWorld"。 展开为:
    printf("Concatenation: %s\n", "HelloWorld");
    
    输出:Concatenation: HelloWorld
## 是 C/C++ 宏预处理器的 标记粘贴操作符(Token-pasting operator)

操作符将 valueName 粘贴到 aaa 和 bbb 之间,形成新的标识符。例如,使用 aaa##valueName##bbb 这样的语法是完全有效的。

用于将宏参数和宏内的其他标记连接起来。具体来说,##Value##Initialized 这两个标记会与宏参数(如 valueName)进行拼接,形成新的标识符。

#define REGISTER_OPTION_CACHE(type, valueName, ...)                 \
    static thread_local type valueName##Value;                      \
    static thread_local bool valueName##Initialized = false;        \
    inline type GetWithCache##valueName() {                         \
        if (!valueName##Initialized) {                              \
            valueName##Value = __VA_ARGS__();                       \
            valueName##Initialized = true;                          \
        }                                                           \
        return valueName##Value;                                    \
    }                                                               \
    inline void SetWithCache##valueName(type value) {               \
        valueName##Value = value;                                   \
        valueName##Initialized = true;                              \
    }

宏展开解释

假设你在代码中使用了如下调用:

REGISTER_OPTION_CACHE(int, MyValue, 42);

这将会将宏中的 type 替换为 intvalueName 替换为 MyValue,并且 __VA_ARGS__ 代表了传递给宏的可变参数 42

展开后的代码:

static thread_local int MyValueValue;
static thread_local bool MyValueInitialized = false;

inline int GetWithCacheMyValue() {
    if (!MyValueInitialized) {
        MyValueValue = 42;  // 使用了 __VA_ARGS__
        MyValueInitialized = true;
    }
    return MyValueValue;
}

inline void SetWithCacheMyValue(int value) {
    MyValueValue = value;
    MyValueInitialized = true;
}

关键点解释:

  • valueName##Value 被展开为 MyValueValue## 操作符将宏参数 MyValueValue 连接,形成新的标识符 MyValueValue
  • valueName##Initialized 被展开为 MyValueInitialized,同样是将宏参数 MyValueInitialized 连接,形成新的标识符 MyValueInitialized

结果

  • MyValueValue 存储了缓存的值(在这个例子中是 42)。
  • MyValueInitialized 是一个布尔值,用来标记缓存是否已经初始化。
  • GetWithCacheMyValue() 函数首先检查 MyValueInitialized 是否为 true,如果没有被初始化,它会使用 42 来初始化缓存并设置标志。
  • SetWithCacheMyValue(int value) 函数允许你更新缓存的值,并将 MyValueInitialized 设置为 true
  • const:
    • const用于声明一个常量,指示标识符的值在程序执行期间不能被修改。
    • const可以用于变量、函数参数、函数返回类型和成员函数。使用const可以提高代码的可读性和安全性。
    • 例如:const int MAX_VALUE = 100;,声明一个名为MAX_VALUE的常量。
  • typedef:
    • typedef用于为数据类型创建别名。它可以用于为复杂的数据类型提供更简洁的名称,增强代码的可读性和可维护性。
    • typedef创建的别名可以像原始类型一样使用,并且不会引入新的类型,只是为已有类型提供了一个新的名称。
    • 例如:typedef int Age;,为int类型创建了一个别名Age。
  • inline:
  • inline用于声明内联函数,它是一种编译器的建议,用于将函数的定义直接插入到调用处,以避免函数调用的开销。
  • 内联函数通常在函数体较小且频繁调用的情况下使用,可以提高程序的执行效率。
  • inline关键字只是给编译器一个提示,编译器可以选择忽略该提示。在大多数情况下,编译器会自动进行内联优化。
  • 例如:inline int add(int a, int b) { return a + b; },声明了一个内联函数add。

  • define主要用于宏定义,const用于声明常量,typedef用于创建类型别名,inline用于内联函数的声明。

#ifndef & #pragma once

为了避免同一个文件被include多次,C/C++中有两种方式,一种是#ifndef方式,一种是#pragma once方式。在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一些细微的区别

new & delete

  • new和delete 相对于 malloc/free 分配和释放堆空间。
  • 额外会执行构造函数和析构函数
#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructing MyClass" << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructing MyClass" << std::endl;
    }
};

int main() {
    // 使用new动态分配内存,并调用构造函数
    MyClass* obj = new MyClass();

    // 执行一些操作...

    // 使用delete释放内存,并调用析构函数
    delete obj;

    return 0;
}

namespace

namespace 会影响 typedef 的作用范围,但不会直接限制 #define 宏的作用范围。

头文件

  • 相互引用,前置声明
  • include头文件其实就是将对应的头文件内容贴在include的位置

A.h, B.h 都需要string.h的头文件,然后B.h 会include A.h,那么我在B.h里是不是可以省略include string.h

不应该省略,

  1. 防止代码变更引发的问题: 如果某天 A.h 中移除了 #include ,而 B.h 依赖 A.h 提供的 #include ,那么 B.h 将会因找不到 std::string 而编译失败。因此,显式包含依赖的头文件可以避免这种隐含依赖引发的问题。
  2. 提高可读性和自包含性: 每个头文件应该尽量做到自包含,意思是每个头文件应该独立地包含所有它所需要的头文件。这样做的好处是,任何其他文件都可以安全地单独包含 B.h,而无需额外关心它依赖于哪些头文件。
  3. 减少隐式依赖: 隐式依赖(依赖另一个头文件帮你包含所需的头文件)可能导致维护性问题。显式 #include 可以让代码更具可预测性和可维护性。

include的位置有什么规则和规律吗,头文件和cpp文件前都可以吗?

在编写代码时,往往A.cpp需要include A.h。那A.cpp需要的头文件,我是写在A.cpp里还是A.h里?

  1. 头文件 (A.h):只包含声明所需的头文件,不包含仅在实现中需要的头文件。
  2. 源文件 (A.cpp):包含所有实现需要的头文件,特别是那些仅在实现部分用到的头文件。此外,A.cpp 应该总是包含 A.h。6.

函数的特殊写法

函数传参

  1. 值传递
  2. 引用传递
  3. 指针传递
//值传递
change1(n);
void change1(int n){
    n++;
}

//引用传递,操作地址就是实参地址 ,只是相当于实参的一个别名,在符号表里对应是同一个地址。对它的操作就是对实参的操作
    change2(n);
    void change2(int &n){
        n++;
    }
    //特殊对vector
    void change2(vector<int> &n)
    //特殊对数组
    void change2(int (&n)[1000])

//指针传递,其实是地址的值传递
change3(&n);
void change3(int *n){
    *n=*n+1;
}

引用传递和指针传递的区别:

  • 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
  • 不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
  • 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。

指针传递和引用传递的使用情景:

  1. 函数内部修改参数并且希望改动影响调用者。
  2. 当一个函数实际需要返回多个值,而只能显式返回一个值时,可以将另外需要返回的变量以指针/引用传递

闭包、匿名函数

闭包是捕获并持有了外部作用域变量的函数。

闭包(Closure)是指在程序中,函数可以捕捉并记住其作用域(环境)中的变量,即使在函数执行完成后,这些变量依然保存在内存中,并能在后续的函数调用中被使用。闭包的一个重要特性是,它不仅保存了函数本身的逻辑,还“闭合”了函数执行时的上下文环境(即该函数所在的作用域)。

闭包通常用于实现函数内部的状态保持、回调函数等场景。在 C++ 中,闭包通过 lambda 表达式 实现,lambda 表达式可以捕获外部变量并在其内部使用。

例子

auto add = [](int x) {
    return [x](int y) {
        return x + y;
    };
};

auto add5 = add(5);
std::cout << add5(3); // 输出 8
在上面的例子中,add 函数返回了一个闭包,捕获了变量 x 的值。即使 x 在原作用域中不再可用,返回的闭包仍然可以访问并使用 x 的值。

  • 匿名函数是一种没有被绑定标识符的函数
  • lambda 是一种匿名函数
  • lambda 可以表示闭包

匿名函数(lambda)和闭包的关系就如同类和类对象的关系

匿名函数和类的定义都只存在于源码(代码段)中,而闭包和类对象则是在运行时占用内存空间的实体;

传参默认值

虽然理论上可以通过类似void f(bool x = true)来实现默认值。

// 函数声明
void bar(int a = 10);

// 函数定义
void bar(int a) {
    std::cout << "a: " << a << std::endl;
}
有时(复杂项目)会编译.so不过, 会出现undefied的符号。

导致实际编码如下:

// hpp
void SetDeterministic();
void SetDeterministic(bool isOpapi = true);

//cpp
void SetDeterministic() {
    SetDeterministic(true);
}
void SetDeterministic(bool isOpapi)
{
    //xxx
}

类型变参模板

template<typename T>
void swap(T& t1, T& t2)
{
    T temp = t2;
    t2 = t1;
    t1 = temp;
}
swap<int>(a,b);
条件模板类

假设我们有一个模板类 Wrapper,我们希望禁止 VirtualGuardImpl 类型作为模板参数:

template <
    typename T,
    typename U = T,
    typename = typename std::enable_if<!std::is_same<U, VirtualGuardImpl>::value>::type>
class Wrapper {
public:
    void function() {
        // 实现
    }
};

在这个例子中,如果用户尝试创建 Wrapper<VirtualGuardImpl>Wrapper<VirtualGuardImpl, VirtualGuardImpl> 的实例,编译器将报错,因为 std::enable_if 的条件不满足。但如果使用其他类型,比如 int 或自定义类型,就可以正常编译。

这段代码是 C++ 中的一个模板函数或模板类模板参数的定义,它使用了模板默认参数、std::enable_if 条件编译技术以及类型萃取(type traits)。下面是对这段代码的详细解释:

  1. 模板参数 U:

    • typename U = T 定义了一个模板类型参数 U,并给它一个默认值 T。这意味着如果在使用模板时没有指定 U 的话,它将默认使用模板参数 T 的值。
  2. std::enable_if:

    • std::enable_if 是一个条件编译技术,它只在给定的布尔表达式为 true 时启用某个模板。
    • 在这个例子中,std::enable_if 后面的布尔表达式是 !std::is_same<U, VirtualGuardImpl>::value。这意味着只有当 U 不等于 VirtualGuardImpl 类型时,这个模板参数才有效。
  3. typename 关键字:

    • typename 关键字用于告诉编译器 std::enable_if 的结果是一个类型。std::enable_if 返回的是一个类型,如果条件为 true,它返回一个空的类型,否则会导致编译错误。
  4. std::is_same:

    • std::is_same<U, VirtualGuardImpl>::value 是一个编译时检查,用于判断 UVirtualGuardImpl 是否是相同的类型。::value 是类型特征 std::is_same 的一个成员,它是一个布尔值,如果类型相同则为 true,否则为 false
  5. 组合解释:

    • 这段代码的意思是:定义一个模板参数 U,默认值为 T,并且这个模板参数只有在 U 不是 VirtualGuardImpl 类型时才有效。
    • 这是一种常见的模板编程技巧,用于约束模板参数的类型,以确保它们符合特定的要求。

个数变参模板

#include <stdarg.h>
void Error(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    vfprintf(stderr, format, argptr);
    va_end(argptr);
}

VA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。

  1. 首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
  2. 然后用VA_START宏初始化变量刚定义的VA_LIST变量;
  3. 然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
  4. 最后用VA_END宏结束可变参数的获取。

系统提供了vprintf系列格式化字符串的函数,用于编程人员封装自己的I/O函数。

int vprintf  / vscanf   (const char * format, va_list ap);                  // 从标准输入/输出格式化字符串 
int vfprintf / vfsacanf (FILE * stream, const char * format, va_list ap);   // 从文件流 
int vsprintf / vsscanf  (char * s, const char * format, va_list ap);        // 从字符串

返回多个数

使用结构体

struct RowAndCol { int row;int col; };

RowAndCol r(string fn) {
    /*...*/
    RowAndCol result;
    result.row = x;
    result.col = y;
    return result;
}

左值与右值

在 C++ 中,左值(lvalue)右值(rvalue) 是两个重要的概念,用来描述表达式的值和内存的关系。它们帮助开发者理解变量的生命周期、赋值和对象管理,特别是在现代 C++ 中引入了右值引用后,优化了移动语义和资源管理。

1. 左值(lvalue)

左值(lvalue,locatable value) 是指在内存中有明确地址、可持久存在的对象,可以对其进行赋值操作。通俗地说,左值是能够取地址的值,可以出现在赋值操作符的左边。

特点:

  • 左值具有持久的内存地址。
  • 左值可以取地址(使用 & 运算符)。
  • 左值通常表示已经存在的变量或对象。

示例

int x = 10;   // x 是左值
int* p = &x;  // 可以取 x 的地址

x = 20;       // 可以对左值进行赋值

在这个例子中,x 是一个左值,因为它表示了内存中的某个对象,并且可以通过赋值语句修改它的值。

2. 右值(rvalue)

右值(rvalue,readable value) 是没有明确地址、临时存在的对象,不能对其进行赋值操作。它们通常是字面值常量或表达式的结果。右值只能出现在赋值操作符的右边,表示一个临时对象或数据。

特点:

  • 右值是临时的,通常会在表达式结束时销毁。
  • 右值不能取地址(即不能使用 & 获取右值的地址)。
  • 右值表示表达式的计算结果或临时对象。

示例

int y = 10;       // 10 是右值
int z = y + 5;    // y + 5 是右值表达式

在这个例子中,10y + 5 是右值,因为它们表示计算出的临时数据,并且不能直接对这些值进行赋值操作。

3. 现代 C++ 中的右值引用(rvalue reference)

C++11 引入了 右值引用,即通过 && 符号表示。这使得右值也能通过引用进行操作,特别是在实现移动语义(move semantics)和避免不必要的拷贝时非常有用。右值引用允许我们通过右值管理资源,避免性能上的损失。

示例:右值引用与移动语义

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec1 = {1, 2, 3};
    std::vector<int> vec2 = std::move(vec1);  // vec1 资源移动到 vec2

    std::cout << "vec1 size: " << vec1.size() << std::endl;
    std::cout << "vec2 size: " << vec2.size() << std::endl;

    return 0;
}

在这个例子中,std::movevec1 变为一个右值引用,使其内部的资源(如动态分配的内存)直接转移给 vec2,避免了拷贝。

4. 区分左值与右值

通常,左值是表示持久存在的对象,可以通过取地址符 & 获取其地址,而右值是临时的、短暂存在的值,不能直接获取其地址。理解这两者对于编写高效的 C++ 代码和使用现代特性(如右值引用和移动语义)非常重要。

常见误区

  • 字面值常量(如 42、'a')是右值
  • 表达式的结果(如 x + y)通常是右值。
  • 函数返回值若返回的是值,而不是引用,则该返回值是右值。
  • 左值(lvalue) 是可以取地址的值,通常是变量或持久的对象。
  • 右值(rvalue) 是临时值,通常是表达式的结果或字面量。
  • 右值引用(&&)是 C++11 引入的新特性,用来优化资源管理和避免不必要的拷贝操作。

C++11: 花括号初始化列表

使用

在C++98/03中我们只能对普通数组和POD(plain old data,简单来说就是可以用memcpy复制的对象)类型可以使用列表初始化,如下:

//数组的初始化列表: 
int arr[3] = {1,2,3}
//POD类型的初始化列表:
struct A
{
 int x;
 int y;
}a = {1,2};

在C++11中初始化列表被适用性被放大,可以作用于任何类型对象的初始化。如下:

X x1 = X{1,2};
X x2 = {1,2}; // 此处的'='可有可⽆
X x3{1,2};
X* p = new X{1,2};

//列表初始化也可以用在函数的返回值上
std::vector<int> func() {
    return {};
}

变量类型的适用范围

聚合类型可以进行直接列表初始化

聚合类型包括

  1. 普通数组,如int[5],char[],double[]等
  2. 一个类,且满足以下条件:
    1. 没有用户声明的构造函数
    2. 没有用户提供的构造函数(允许显示预置或弃置的构造函数)
    3. 没有私有或保护的非静态数据成员
    4. 没有基类
    5. 没有虚函数
    6. 没有{}和=直接初始化的非静态数据成员
    7. 没有默认成员初始化器

原理

对于一个聚合类型,使用列表初始化相当于使用std::initializer_list对其中的相同类型T的每个元素分别赋值处理,类似下面示例代码;

struct CustomVec {
    std::vector<int> data;
    CustomVec(std::initializer_list<int> list) {
        for (auto iter = list.begin(); iter != list.end(); ++iter) {
            data.push_back(*iter);
        }
    }
};

优势

  1. 方便,且基本上可以替代括号初始化
  2. 可以使用初始化列表接受任意长度
  3. 可以防止类型窄化,避免精度丢失的隐式类型转换

参考文献

  1. https://zh.cppreference.com/

  2. https://leetcode-cn.com/problems/path-with-maximum-gold/solution/huang-jin-kuang-gong-by-leetcode-solutio-f9gg/

  3. https://blog.csdn.net/qq_33221533/article/details/82119031

  4. ⼩贺 C++ ⼋股⽂ PDF 的作者,电⼦书的内容整理于公众号「herongwei」

  5. https://blog.csdn.net/hailong0715/article/details/54018002

https://shaojiemike.notion.site/C-11-a94be53ca5a94d34b8c6972339e7538a

6 FPS

卡拉彼丘

  • 信息差:发现分布以及落单对方,灵活跑动隐藏自身,干扰对方准备好时,对方换弹/倒地时补射。
  • 躲避对方多枪线,己方架多枪线,多路线包围
  • 一个位置偷一枪,就换位置。不要再露头。
  • 对枪注意弦化
  • 位置的选择:一要有掩体,二要有安全的退路通道和队友大部队汇合,不要被敌人包夹。
  • 不要急于补人,要观察有没有被敌人包
  • 进阶:时刻预瞄出人点,
    • 弦化靠左墙,预瞄靠左,因为向右出掩体,准星会被向右移动。
    • 学习弹道,反向压枪。
  • 注意不要冲动,以身试陷(除非是突破位)

角色特点

  1. 熊当掩体(带闪光弹,烟雾弹),熊会自动冲锋并结冰
  2. 防守方
  3. 米雪儿:适合压制补枪,技能适合补枪。引诱敌方到背面炮台射程里
  4. 进攻方:
  5. 明:侦察 + 干扰器,风场雷

地图,高空卡墙脚。

  1. 欧拉港口/海湾图:复杂的短距离(掩体之间的距离)小路。适合白墨(带烟雾弹增加自身能力)和熊。白墨攻击走中间,抄底路偷对面的大狙。或者A点上上下下,适合近身跳散弹。
  2. 防守走A
  3. 404基地/巨炮图:白墨可以中路强压。
  4. 防守方 熊,进攻方沙猫无敌B
  5. 88区/古风图,大图远视野,适合大狙,大机枪。还有熊
  6. 禁止白墨。
  7. 风曳镇:大狙和小画家
  8. 防守必选熊(AB滑)和信(传送)
  9. 禁止白墨。

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。

Scons

Scons

  • SCons is a software construction tool that can be used as an alternative to traditional build systems like Make and CMake.
  • It is a Python-based build tool that provides a convenient and flexible way to define and manage the build process for software projects, including C++ programs.

Scons VS cmake

  1. 基于python语言的构建工具,对开发者来说过度自然,简单,no need to learn domain-specific language like cmake

其余cmake有的, Scons 也有。

  1. cross-paltform,
  2. SCons has built-in support for dynamic dependency analysis, meaning it can automatically detect changes in source files and rebuild only what's necessary. This can result in faster builds for large projects.

Project structure

  1. Sconstruct python file as compile entry

framework grammar

  1. add option for scons command
AddOption('--buildDir', 
   dest='buildDir', 
   type='string', 
   default="build/", 
   # default=False,
   nargs=1, 
   action='store', # meaning save the string
   # or action='store', meaning True or false
   metavar='DIR', 
   help='Base build directory'
)
baseBuildDir = GetOption('buildDir') 
  1. add sub scons config file and build result path using variant_dir
env.SConscript("src/SConscript", variant_dir=buildDir, exports= {'env' : env.Clone()})     
  1. achive debug mode

using scons debug=1 command.

env = Environment()
debug = ARGUMENTS.get("debug", 0)
if int(debug):
   print "in debug mode"

main construct grammar

  1. Define the Build Environment: In the SConstruct file, define the build environment by creating an Environment object. You can specify compiler options, flags, include paths, library paths, and other build settings within this object.
env = Environment(CXX='g++', CCFLAGS=['-O2', '-Wall'], CPPPATH=['include'], LIBPATH=['lib'])
 libEnv = env.Clone()
  1. Specify Source Files and Targets: Define the source files for your C++ program and specify the target(s) you want to build using the Program() function.
source_files = ['main.cpp', 'util.cpp', 'other.cpp']
# or select the src files 
Object('hello.cpp')
program = env.Program(target='my_program', source=source_files)

In this example, main.cpp, util.cpp, and other.cpp are the source files, and my_program is the name of the target executable.

  1. static or dynamic lib
# static
Library("t", Glob("src/*.cpp"))
# dynamic
source = Glob("src/*.cpp")
SharedLibrary("t", source)
Program(["hello.cpp"], LIBS=["t"], LIBPATH=".")
  1. execute command during compilation

  2. this is usually to print info

  3. The command is executed when any of the specified dependencies (allSrcs, ".git/index", or "SConstruct") change.
env.Command(
 target='bar.out',
 source='bar.in',
 action=["rm -f $TARGET", "$BAR_BUILD < $SOURCES > $TARGET"],
 ENV={'PATH': '/usr/local/bin/'},
)
env.Command(
   versionFile, 
   allSrcs + [".git/index" "SConstruct"],                                                 
   'printf "#define ZSIM_BUILDDATE \\"`date "+%Y-%m-%d %T"`\\"\\n#define ZSIM_BUILDVERSION \\"`python misc/getver.py`\\"" >>' + versionFile) 

Command

scons -c  # Clean
scons debug=1    # Rebuild using `SConstruct` file in debug mode

scons-project analysis

TODO: multipim how to add a singel head file during compilation process.

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。

https://scons.org/doc/production/PDF/scons-man.pdf

LinuxFolderInstall

system folders structure

根据教程

  1. /etc:是 Etcetera(等等) 的缩写,这个目录用来存放所有的系统管理所需要的配置文件和子目录。
  2. 这个是系统中的配置文件,如果你更改了该目录下的某个文件可能会导致系统不能启动。各种服务(ssh,apache2,nfs)的配置文件
  3. /lib:是 Library(库) 的缩写这个目录里存放着系统最基本的动态连接共享库,其作用类似于 Windows 里的 DLL 文件。几乎所有的应用程序都需要用到这些共享库。
  4. /opt:是 optional(可选) 的缩写,这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。
  5. /usr:是 unix shared resources(共享资源) 的缩写,这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于 windows 下的 program files 目录。It's install for all user.
  6. /var:是 variable(变量) 的缩写,这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件

sudo install path

TODO: during the application install, which locations those app used?

I guess it's usr/bin or /include , opt and /lib

  1. GNU G++
  2. cuda
  3. intel one-api

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。