欢迎光临
我们一直在努力

Makefile进阶(下)

一、Makefile在多模块项目中的核心作用

在真实的软件开发中,项目往往由多个模块组成,每个模块可能包含若干源文件、头文件,甚至可能独立编译为静态库或动态库。Makefile作为自动化构建工具,其核心价值在于管理依赖关系、自动化编译过程、支持增量编译、统一构建流程。在多模块项目中,一个设计良好的Makefile能够显著提升开发效率,降低维护成本,并支持团队协作。

原文中通过一个自动化脚本(mkcode.sh)生成了100个C源文件及其对应的头文件,并将其分组到10个模块(Module0~Module9)中。这种结构模拟了中大型项目的典型组织方式:模块化分离、功能内聚、接口清晰。每个模块具有独立的目录,包含自己的源文件、头文件和一个用于测试的main.c,这种结构便于独立开发、测试和复用。

二、多模块Makefile的设计思路

2.1 模块化构建 vs 整体构建

在构建多模块项目时,通常有两种策略:

  • 整体构建:所有源文件一起编译,生成单一可执行文件。适合模块间耦合度高、难以分离的项目。

  • 模块化构建:每个模块独立编译,最后链接成可执行文件。适合模块清晰、接口明确、可能独立测试或复用的项目。

原文中给出的第一个Makefile示例属于整体构建,它通过变量MODULES收集所有模块路径,使用wildcard函数获取所有.c文件,统一编译到objs目录下,最后链接为project。这种方式的优点是构建逻辑集中、依赖清晰、易于管理全局编译选项,缺点是模块间独立性弱,不利于并行编译和模块复用。

第二种方式(多Makefile方式)则属于模块化构建,每个模块有自己的Makefile,可以独立编译为可执行文件(如project0~project9),顶层Makefile仅负责递归调用各模块的构建过程。这种方式支持并行编译、便于模块独立测试、适合持续集成,但需要维护多个Makefile,且需统一编译选项和输出目录。

2.2 依赖管理的关键技巧

在多模块Makefile中,依赖关系的正确声明至关重要。原文中使用了以下技巧:

  • 自动收集源文件:

SRCS := $(foreach module,$(MODULES),$(wildcard $(module)/*.c))

这行代码遍历每个模块目录,收集所有.c文件,避免手动枚举。

  • 对象文件输出到统一目录:

OBJS_DIR = objs
OBJS := $(patsubst %.c,$(OBJS_DIR)/%.o,$(SRCS))

将所有.o文件输出到objs目录下,保持源码目录干净,便于清理。

  • 头文件路径管理:

OBJS_DIR = objs
OBJS := $(patsubst %.c,$(OBJS_DIR)/%.o,$(SRCS))

为每个模块目录添加-I选项,确保编译器能找到对应头文件。

2.3 支持增量编译

Makefile基于时间戳判断是否需要重新编译,这是其核心优势之一。在多模块项目中,应确保:

  • 每个.c文件只在其自身或依赖的头文件更新时重新编译。

  • 最终可执行文件只在任一.o文件更新时重新链接。

  • 原文中的模式规则实现了这一点:

    $(OBJS_DIR)/%.o: %.c
    @$(MKDIR) -p $(dir $@)
    @$(CC) -c $< -o $@ $(INCLUDES)

    该规则会为每一个.c文件生成对应的.o文件,且仅当.c或通过INCLUDES引入的头文件更新时触发编译。

    三、自动化脚本与Makefile的协同

    原文中的mkcode.sh脚本展示了如何自动化生成项目结构,这在以下场景中特别有用:

    • 快速搭建测试框架

    • 模拟大型项目结构

    • 自动化生成重复代码

    该脚本通过几个函数实现不同功能:

    • mkinclude:生成头文件,内容固定,包含#pragma once和函数声明。

    • mkcode:根据模板文件生成源文件,替换函数名。

    • mkmain:生成调用所有函数的main.c。

    • mkmodule:将文件按模块分组移动。

    • mkfile:在每个模块目录中生成子Makefile。

    这种“代码生成 + Makefile 构建”的模式,在原型开发、教学演示、自动化测试中非常高效。

    四、多Makefile项目的管理策略

    在拥有多个子Makefile的项目中,顶层Makefile的角色是协调者,负责:

    1. 递归调用子目录构建

    build:
    @for module in $(MODULES); do \\
    (cd $$module && make) || exit 1; \\
    echo "build $$module … done"; \\
    done

    2. 统一清理

    clean:
    @for module in $(MODULES); do \\
    (cd $$module && make clean); \\
    echo "clean $$module … done"; \\
    done

    3. 统一发布

    output:
    @for module in $(MODULES); do \\
    (cd $$module && make output); \\
    done

    这种结构使得每个模块可以独立编译、测试,同时又能通过顶层Makefile统一管理,适合微服务架构、插件系统、多平台项目等场景。

    五、实用建议与常见陷阱

    5.1 建议

    • 保持Makefile简洁:尽量使用变量、函数和模式规则,避免重复代码。

    • 支持外部配置:可通过CFLAGS、LDFLAGS等变量允许外部传入编译选项。

    • 输出友好信息:使用@echo输出构建进度,便于调试和监控。

    • 支持并行构建:使用make -j N可加速构建,确保Makefile能正确处理依赖。

    5.2 常见陷阱

    • 路径问题:在递归调用Makefile时,注意相对路径与绝对路径的使用。

    • 依赖遗漏:若头文件未在依赖中声明,可能导致增量编译失效。

    • 清理不彻底:清理规则应删除所有生成文件,包括中间文件和最终目标。

    • 平台兼容性:不同平台的shell、mkdir、rm等命令选项可能不同,应尽量使用通用写法。

    六、扩展思考:从Makefile到现代构建系统

    尽管Makefile在Unix/Linux世界中经久不衰,但在跨平台、多语言、依赖管理复杂的现代项目中,其局限性也逐渐显现。以下是几种常见的现代化替代或补充方案:

    • CMake:跨平台构建系统,可生成Makefile、Visual Studio项目等。

    • Bazel:Google开源的构建工具,强调可重复性和高性能。

    • Meson:注重速度与用户体验的构建系统。

    • Autotools(Autoconf/Automake):适用于跨平台开源项目的经典工具链。

    即便使用这些工具,理解Makefile的基本原理依然重要,因为它们最终往往还是会生成Makefile或类似的构建脚本。

    场景5: 引⼊头⽂件

    ⾃动⽣成头⽂件

    #!/ bin / bash
    num = 100 suffix =.c
    include =.h template = template.txt
    function
    mkinclude()
    {
    cnt = 1 while[$cnt – le $num] do echo "#pragma once" >> code${cnt} ${include} echo "#include<stdio.h>" > code${cnt} ${include} echo "void fun${cnt}();" >> code${cnt} ${include} let cnt++ done
    }
    function mkcode()
    {
    cnt = 1 while[$cnt – le $num] do sed "s/fun/fun${cnt}/" ${template} > code${cnt} ${suffix};
    let cnt++;
    done
    }
    function mkmain()
    {
    cnt = 1 > main.c printf "#include <stdio.h>\\n" > main.c while[$cnt – le $num] do printf "#include \\"code$cnt.h\\"\\n" >> main.c
    let cnt++;
    done
    cnt = 1 printf "int main()\\n{\\n" >> main.c while[$cnt – le $num] do printf " fun${cnt}();\\n" let cnt++ done >> main.c echo "}" >> main.c
    }
    function mkmodule()
    {
    i = 0 j = 1 for ((; i < 10; i++)) do((end = j + 10))
    printf "build Module%d\\n" $i
    mkdir –
    p Module$i for ((; j < end; j++)) do mv code${j}.c Module$i
    mv code${j}
    .h Module$i
    #mv Module$i / code${j }.c.#恢复⽂件结构
    done
    done
    mkdir –
    p main
    mv main.c main
    }
    function clean(){
    rm – f *.c rm – f *.h rm – rf Module * rm – rf main} function Usage(){
    printf "Usage: $0 [-mkcode/-mkinclude/-mkmain/-mkmodule/-clr]\\n"} function main()
    {
    if
    [$ # – ne 1];
    then
    Usage return fi
    opt = $1 if[$opt == "-mkcode"];
    then
    mkcode
    elif[$opt == "-mkinclude"];
    then
    mkinclude
    elif[$opt == "-mkmain"];
    then
    mkmain
    elif[$opt == "-mkmodule"];
    then
    mkmodule
    elif[$opt == "-clr"];
    then
    clean else Usage fi
    }
    main $ @

    $ ./mkcode.sh -mkcode
    $ ./mkcode.sh -mkinclude
    $ ./mkcode.sh -mkmain
    $ ./mkcode.sh -mkmodule

    更改makefile

    # main⼦模块名称
    MODULES := main
    # 获取其他模块的名称
    MODULES += $(shell ls -d Module*)

    # 形成⽬标⽂件的名称
    BIN := project
    RMF := rm -f
    MKDIR := mkdir

    # 循环获取指定模块下的所有.c源⽂件
    SRCS := $(foreach module, $(MODULES), $(wildcard $(module)/*.c))
    INCLUDES :=$(addprefix -I, $(MODULES))

    # 将所有的.c换成.o
    # OBJS := $(SRCS:.c=.o)

    # 给OBJS添加临时⽬录
    OBJS_DIR=objs
    OBJS := $(patsubst %.c, $(OBJS_DIR)/%.o, $(SRCS))

    # 基本命令
    CC=gcc

    # 编译⽬标程序
    $(BIN):$(OBJS)
    @$(CC) $^ -o $@
    @echo "连接 $^ 形成 $@"

    # 形成⽬标⽂件
    $(OBJS_DIR)/%.o:%.c
    @$(MKDIR) -p $(dir $@)
    @$(CC) -c $< -o $@ $(INCLUDES)
    @echo "编译$< 形成 $@"

    # 清理项⽬
    .PHONY:clean
    clean:
    @$(RMF) $(OBJS) $(BIN)
    @echo "清理 $(OBJS) $(BIN)"

    # 测试,查看测试结果
    .PHONY:test
    test:
    @echo $(MODULES)
    @echo "———————–"
    @echo $(SRCS)
    @echo "———————–"
    @echo $(OBJS)
    @echo "———————–"
    @echo $(INCLUDES)
    @echo $(INCS)
    @echo "———————–"

    编译

    $ make
    编译main/main.c 形成 objs/main/main.o
    编译Module0/code3.c 形成 objs/Module0/code3.o

    编译Module9/code93.c 形成 objs/Module9/code93.o
    编译Module9/code98.c 形成 objs/Module9/code98.o
    编译Module9/code96.c 形成 objs/Module9/code96.o
    连接 objs/main/main.o objs/Module0/code3.o objs/Module0/code5.o
    objs/Module0/code2.o objs/Module0/code7.o objs/Module0/code10.o
    objs/Module0/code8.o objs/Module0/code4.o objs/Module0/code9.o
    objs/Module0/code1.o objs/Module0/code6.o objs/Module1/code11.o
    objs/Module1/code17.o objs/Module1/code12.o objs/Module1/code16.o
    objs/Module1/code18.o objs/Module1/code15.o objs/Module1/code14.o
    objs/Module1/code13.o objs/Module1/code19.o objs/Module1/code20.o
    objs/Module2/code24.o objs/Module2/code27.o objs/Module2/code23.o
    objs/Module2/code28.o objs/Module2/code29.o objs/Module2/code21.o
    objs/Module2/code22.o objs/Module2/code30.o objs/Module2/code25.o
    objs/Module2/code26.o objs/Module3/code39.o objs/Module3/code37.o
    objs/Module3/code31.o objs/Module3/code35.o objs/Module3/code40.o
    objs/Module3/code32.o objs/Module3/code36.o objs/Module3/code34.o
    objs/Module3/code38.o objs/Module3/code33.o objs/Module4/code41.o
    objs/Module4/code45.o objs/Module4/code42.o objs/Module4/code48.o
    objs/Module4/code44.o objs/Module4/code49.o objs/Module4/code43.o
    objs/Module4/code46.o objs/Module4/code50.o objs/Module4/code47.o
    objs/Module5/code58.o objs/Module5/code59.o objs/Module5/code57.o
    objs/Module5/code51.o objs/Module5/code60.o objs/Module5/code52.o
    objs/Module5/code54.o objs/Module5/code56.o objs/Module5/code53.o
    objs/Module5/code55.o objs/Module6/code70.o objs/Module6/code61.o
    objs/Module6/code64.o objs/Module6/code62.o objs/Module6/code67.o
    objs/Module6/code65.o objs/Module6/code68.o objs/Module6/code63.o
    objs/Module6/code69.o objs/Module6/code66.o objs/Module7/code73.o
    objs/Module7/code74.o objs/Module7/code80.o objs/Module7/code77.o
    objs/Module7/code78.o objs/Module7/code72.o objs/Module7/code71.o
    objs/Module7/code79.o objs/Module7/code75.o objs/Module7/code76.o
    objs/Module8/code85.o objs/Module8/code90.o objs/Module8/code86.o
    objs/Module8/code89.o objs/Module8/code81.o objs/Module8/code87.o
    objs/Module8/code88.o objs/Module8/code82.o objs/Module8/code84.o
    objs/Module8/code83.o objs/Module9/code100.o objs/Module9/code95.o
    objs/Module9/code94.o objs/Module9/code99.o objs/Module9/code91.o
    objs/Module9/code92.o objs/Module9/code97.o objs/Module9/code93.o
    objs/Module9/code98.o objs/Module9/code96.o 形成 project

    $ tree Module0
    Module0
    ├── code10.c
    ├── code10.h
    ├── code1.c
    ├── code1.h
    ├── code2.c
    ├── code2.h
    ├── code3.c
    ├── code3.h
    ├── code4.c
    ├── code4.h
    ├── code5.c
    ├── code5.h
    ├── code6.c
    ├── code6.h
    ├── code7.c
    ├── code7.h
    ├── code8.c
    ├── code8.h
    ├── code9.c
    └── code9.h

    场景6:多 Makefile ,多可执⾏程序

    可以把之前的内容看做项⽬的⼀个部分,拷⻉形成多个部分,也可以⽤如下脚本⾃动形成:

    mkcode.sh

    $ cat mkcode.sh
    #!/ bin / bash
    num = 100 suffix =.c
    include =.h template = template.txt
    function mkinclude()
    {
    cnt = 1 while[$cnt – le $num] do echo "#pragma once" >> code${cnt} ${include} echo "#include<stdio.h>" > code${cnt} ${include} echo "void fun${cnt}();" >> code${cnt} ${include} let cnt++ done
    }
    function mkcode()
    {
    cnt = 1 while[$cnt – le $num] do sed "s/fun/fun${cnt}/" ${template} > code${cnt} ${suffix};
    let cnt++;
    done
    }
    function mkmain()
    {
    cnt = 1 > main.c printf "#include <stdio.h>\\n" > main.c while[$cnt – le $num] do printf "#include \\"code$cnt.h\\"\\n" >> main.c
    let cnt++;
    done
    cnt = 1 printf "int main()\\n{\\n" >> main.c while[$cnt – le $num] do printf " fun${cnt}();\\n" let cnt++ done >> main.c echo "}" >> main.c
    }
    function mkmodule()
    {
    i = 0 j = 1 for ((; i < 10; i++)) do((end = j + 10))
    printf "build Module%d\\n" $i
    mkdir –
    p Module$i for ((; j < end; j++)) do mv code${j}.c Module$i
    mv code${j}
    .h Module$i
    #mv Module$i / code${j }.c.#恢复⽂件结构
    done
    done
    mkdir –
    p main
    mv main.c main
    }
    #根据模版makefile,在各个⽬录下形成makefile
    function mkfile()
    {
    i = 0 for ((; i < 10; i++)) do sed "s/project/project${i}/g" subMakefile > Module$i / Makefile
    #构建每⼀个模块的测试main.c
    echo "#include <stdio.h>" >
    Module$i / main.c echo "int main() {}" >> Module$i / main.c done
    }
    function clean(){
    rm – f *.c rm – f *.h rm – rf Module * rm – rf main} function Usage(){
    printf "Usage: $0 [-mkcode/-mkinclude/-mkmain/-mkmodule/-clr/-mkfile]\\n"} function main()
    {
    if
    [$ # – ne 1];
    then
    Usage return fi
    opt = $1 if[$opt == "-mkcode"];
    then
    mkcode
    elif[$opt == "-mkinclude"];
    then
    mkinclude
    elif[$opt == "-mkmain"];
    then
    mkmain
    elif[$opt == "-mkmodule"];
    then
    mkmodule
    elif[$opt == "-mkfile"];
    then
    mkfile
    elif[$opt == "-clr"];
    then
    clean else Usage fi
    }
    main $ @

    样板 subMakefile

    BIN := project
    CC := gcc
    SRCS := $(wildcard *.c)
    OBJS := $(SRCS:.c=.o)

    $(BIN):$(OBJS)
    $(CC) -o $@ $^
    *.o:%.c
    $(CC) -c $<

    .PHONY:clean
    clean:
    rm -f *.o project

    .PHONY:output
    output:
    mkdir -p ../bin
    mv project ../bin
    #mv project ../

    构建好之后,每⼀个模块都可以独⽴编译

    $ tree Module0/
    Module0/
    ├── code10.c
    ├── code10.h
    ├── code1.c
    ├── code1.h
    ├── code2.c
    ├── code2.h
    ├── code3.c
    ├── code3.h
    ├── code4.c
    ├── code4.h
    ├── code5.c
    ├── code5.h
    ├── code6.c
    ├── code6.h
    ├── code7.c
    ├── code7.h
    ├── code8.c
    ├── code8.h
    ├── code9.c
    ├── code9.h
    ├── main.c
    └── Makefile

    整体⽂件⽬录结构

    $ ll
    total 56
    -rw-rw-r– 1 whb whb 426 Oct 8 16:10 Makefile
    -rwxrwxr-x 1 whb whb 2145 Oct 8 15:57 mkcode.sh
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module0
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module1
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module2
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module3
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module4
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module5
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module6
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module7
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module8
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:12 Module9
    -rw-rw-r– 1 whb whb 240 Oct 8 16:11 subMakefile
    -rw-rw-r– 1 whb whb 60 Sep 24 17:06 template.txt

    Makefile结构

    $ find . -name Makefile
    ./Module0/Makefile
    ./Module3/Makefile
    ./Module7/Makefile
    ./Module8/Makefile
    ./Module5/Makefile
    ./Module1/Makefile
    ./Module2/Makefile
    ./Module6/Makefile
    ./Module9/Makefile
    ./Module4/Makefile
    ./Makefile
    $ find . -name main.c
    ./Module0/main.c
    ./Module3/main.c
    ./Module7/main.c
    ./Module8/main.c
    ./Module5/main.c
    ./Module1/main.c
    ./Module2/main.c
    ./Module6/main.c
    ./Module9/main.c
    ./Module4/main.c

    顶级makefile内容

    # 获取其他模块的名称
    MODULES += $(shell ls -d Module*)

    .PHONY: build
    build:
    @for module in $(MODULES); do \\
    (cd $$module && make) || exit 1; \\
    echo "build $$module … done";\\
    done

    .PHONY: clean
    clean:
    @for module in $(MODULES); do \\
    (cd $$module && make clean); \\
    echo "clean $$module … done";\\
    done

    .PHONY: output
    output:
    @for module in $(MODULES); do \\
    (cd $$module && make output); \\
    done

    编译

    $ make
    make[1]: Entering directory `/home/whb/mkfile/Module0'
    gcc -c -o code3.o code3.c
    gcc -c -o code5.o code5.c
    gcc -c -o code2.o code2.c
    gcc -c -o code7.o code7.c
    gcc -c -o code10.o code10.c
    gcc -c -o main.o main.c
    gcc -c -o code8.o code8.c
    gcc -c -o code4.o code4.c
    gcc -c -o code9.o code9.c
    gcc -c -o code1.o code1.c
    gcc -c -o code6.o code6.c
    gcc -o project0 code3.o code5.o code2.o code7.o code10.o main.o code8.o
    code4.o code9.o code1.o code6.o
    make[1]: Leaving directory `/home/whb/mkfile/Module0'
    build Module0 … done

    编译结果

    Module0
    ├── code10.c
    ├── code10.h
    ├── code10.o
    ├── code1.c
    ├── code1.h
    ├── code1.o
    ├── code2.c
    ├── code2.h
    ├── code2.o
    ├── code3.c
    ├── code3.h
    ├── code3.o
    ├── code4.c
    ├── code4.h
    ├── code4.o
    ├── code5.c
    ├── code5.h
    ├── code5.o
    ├── code6.c
    ├── code6.h
    ├── code6.o
    ├── code7.c
    ├── code7.h
    ├── code7.o
    ├── code8.c
    ├── code8.h
    ├── code8.o
    ├── code9.c
    ├── code9.h
    ├── code9.o
    ├── main.c
    ├── main.o
    ├── Makefile
    └── project0

    清理

    $ make clean
    make[1]: Entering directory `/home/whb/mkfile/Module0'
    rm -f *.o project0
    make[1]: Leaving directory `/home/whb/mkfile/Module0'
    clean Module0 … done
    make[1]: Entering directory `/home/whb/mkfile/Module1'
    rm -f *.o project1
    make[1]: Leaving directory `/home/whb/mkfile/Module1'
    clean Module1 … done
    make[1]: Entering directory `/home/whb/mkfile/Module2'
    rm -f *.o project2
    make[1]: Leaving directory `/home/whb/mkfile/Module2'
    clean Module2 … done
    make[1]: Entering directory `/home/whb/mkfile/Module3'
    rm -f *.o project3
    make[1]: Leaving directory `/home/whb/mkfile/Module3'
    clean Module3 … done
    make[1]: Entering directory `/home/whb/mkfile/Module4'

    查看清理结果

    Module0
    ├── code10.c
    ├── code10.h
    ├── code1.c
    ├── code1.h
    ├── code2.c
    ├── code2.h
    ├── code3.c
    ├── code3.h
    ├── code4.c
    ├── code4.h
    ├── code5.c
    ├── code5.h
    ├── code6.c
    ├── code6.h
    ├── code7.c
    ├── code7.h
    ├── code8.c
    ├── code8.h
    ├── code9.c
    ├── code9.h
    ├── main.c
    └── Makefile

    编译并发布

    $ make && make output

    查看结果

    $ ll
    total 60
    2drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 bin
    -rw-rw-r– 1 whb whb 424 Oct 8 16:20 Makefile
    -rwxrwxr-x 1 whb whb 2145 Oct 8 15:57 mkcode.sh
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module0
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module1
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module2
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module3
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module4
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module5
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module6
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module7
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module8
    drwxrwxr-x 2 whb whb 4096 Oct 8 16:23 Module9
    -rw-rw-r– 1 whb whb 240 Oct 8 16:11 subMakefile
    -rw-rw-r– 1 whb whb 60 Sep 24 17:06 template.txt
    $ ll bin
    total 120
    -rwxrwxr-x 1 whb whb 9096 Oct 8 16:23 project0
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project1
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project2
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project3
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project4
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project5
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project6
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project7
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project8
    -rwxrwxr-x 1 whb whb 9112 Oct 8 16:23 project9

    形成多可执⾏程序

    赞(0)
    未经允许不得转载:171主机测评 » Makefile进阶(下)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址