gpt4 book ai didi

c++ - 基于编译器选项的已编译二进制路径

转载 作者:太空狗 更新时间:2023-10-29 21:33:02 27 4
gpt4 key购买 nike

我有一个大型程序和库,都是用C++编写的,并使用make构建。在makefile中设置的大约十二个选项会变成预处理器指令,这些指令会更改实现(使用ifdef等)。现在,当我在运行代码之前更改这些编译器选项时,通过make clean ing强制执行构建过程。我想设置系统,以便二进制文件的名称根据选项而变化。但是,我担心将来会错过一个或添加一个,而忘记更改名称,等等。是否有解决此问题的干净方法?

我考虑过的一些选择:

  • 在创建时手动创建一个二进制名称,例如APP。#{OPT_1}。#{OPT_2}。#{OPT_3}。#{OPT_4},然后运行一个
  • 从所有编译器标志(CXXFLAGS)创建一个哈希(例如SHA1),并将该哈希放入我的二进制文件名称中,例如APP。#{SHA1(CXXFLAGS)}。这具有将来可扩展的值(value)。

  • 有更好的方法/建议吗?

    最佳答案

    Any better approaches / recommendations?



    如果我理解正确,那么您的GNU Make构建系统可以构建以下几种
    您的可执行文件,由已定义(或未定义)的预处理器宏区分开
    在编译命令中,具体取决于在Makefile中测试的条件
    和/或传递给 make的参数上。而您希望能够建立
    这些变体中的任何一个都独立存在,而无需使用 make clean来删除
    先前构建的 Artifact ,很可能是其他构建的 Artifact
    变体。

    这是构建系统的基本需求之一。常规解决方案不是
    您正在考虑的一种-以某种方式将差异编码为
    可执行文件。除非您用的名称执行相同的操作
    链接到可执行文件的目标文件。如果不这样做,那么什么时候
    您从变体X切换到变体Y,即变体X对象文件 foo.o不早于 foo.cpp,则无需重新编译,
    即使应该用于变体-Y,该变体-X foo.o也会链接到
    变量Y可执行文件,无论它叫什么。

    常规解决方案是按变体区分编译器的位置
    将输出目标文件以及相应的链接器位置
    输出可执行文件。毫无疑问,您曾经使用过的所有C / C++ IDE都可以让您
    构建项目的调试版本或发布版本,以及
    它们将调试对象文件和可执行文件与发布对象区分开
    文件和可执行文件,方法是在文件的不同子目录中生成它们
    项目目录,例如
    <projdir>/Debug/{obj|bin}
    <projdir>/Release/{obj|bin}

    或许:
    <projdir>/obj/{debug|release}
    <projdir>/bin/{debug|release}

    这种方法会自动编码目标文件或可执行文件的变体
    进入其绝对路径名,例如
    <projdir>/Debug/obj/foo.o
    <projdir>/bin/release/prog

    事不宜迟,这些变体可以独立构建。

    在makefile中实现此方案很简单。大多数IDE
    使用它的人会在幕后生成的makefile中实现它。
    而且,将方案扩展到除调试之外的更多变体也很简单
    和发布(尽管您想要任何变体,但您肯定会想要调试
    并发布这些变体的变体)。

    这是我们要在任何
    我们将称为两个构建属性的组合获得的变体 TRAIT_ATRAIT_B:
     | TRAIT_A | TRAIT_B |
    |---------|---------|
    | Y | Y |
    |---------|---------|
    | Y | N |
    |---------|---------|
    | N | Y |
    |---------|---------|
    | N | N |

    我们希望能够以 Debug模式或发行版构建任何这些变体
    模式。 TRAIT_{A|B}可能直接映射到预处理器宏或
    预处理器标志,编译器选项和/或链接选项的任意组合。

    我们的程序 prog是仅从一个源文件构建的:

    main.cpp
    #include <string>
    #include <cstdlib>

    int main(int atgc, char * argv[])
    {
    std::string cmd{"readelf -p .GCC.command.line "};
    cmd += argv[0];
    return system(cmd.c_str());
    }

    它所做的只是调用 readelf来转储链接部分 .GCC.command.line在自己的可执行文件中。该链接部分仅在我们编译或
    与GCC选项 -frecord-gcc-switches 链接。
    因此,仅出于演示目的,我们将始终编译并链接该选项。
    这是一个makefile,它采用一种区分所有变体的方式:
    目标文件以 ./obj[/trait...]编译;可执行文件链接到 ./bin[/trait...]:

    Makefile
    CXX = g++
    CXXFLAGS := -frecord-gcc-switches
    BINDIR := ./bin
    OBJDIR := ./obj

    ifdef RELEASE
    ifdef DEBUG
    $(error RELEASE and DEBUG are mutually exclusive)
    endif
    CPPFLAGS := -DNDEBUG
    CXXFLAGS += -O3
    BINDIR := $(BINDIR)/release
    OBJDIR := $(OBJDIR)/release
    endif

    ifdef DEBUG
    ifdef RELEASE
    $(error RELEASE and DEBUG are mutually exclusive)
    endif
    CXXFLAGS += -O0 -g
    BINDIR := $(BINDIR)/debug
    OBJDIR := $(OBJDIR)/debug
    endif

    ifdef TRAIT_A
    CPPFLAGS += -DTRAIT_A # or whatever
    BINDIR := $(BINDIR)/TRAIT_A
    OBJDIR := $(OBJDIR)/TRAIT_A
    endif

    ifdef TRAIT_B
    CPPFLAGS += -DTRAIT_B # or whatever
    BINDIR := $(BINDIR)/TRAIT_B
    OBJDIR := $(OBJDIR)/TRAIT_B
    endif

    SRCS := main.cpp
    OBJS := $(OBJDIR)/$(SRCS:.cpp=.o)
    EXE := $(BINDIR)/prog

    .PHONY: all clean

    all: $(EXE)

    $(EXE): $(OBJS) | $(BINDIR)
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $(LDFLAGS) $^ $(LIBS)

    $(OBJDIR)/%.o: %.cpp | $(OBJDIR)
    $(CXX) -c -o $@ $(CPPFLAGS) $(CXXFLAGS) $<

    $(BINDIR) $(OBJDIR):
    mkdir -p $@

    clean:
    $(RM) $(EXE) $(OBJS)

    现在,让我们在 Debug模式下构建两个变体,并在其中构建另外两个变体
    Release模式,一个接一个
    $ make DEBUG=1 TRAIT_A=1
    mkdir -p obj/debug/TRAIT_A
    g++ -c -o obj/debug/TRAIT_A/main.o -DTRAIT_A -frecord-gcc-switches -O0 -g main.cpp
    mkdir -p bin/debug/TRAIT_A
    g++ -DTRAIT_A -frecord-gcc-switches -O0 -g -o bin/debug/TRAIT_A/prog obj/debug/TRAIT_A/main.o

    $ make DEBUG=1 TRAIT_B=1
    mkdir -p obj/debug/TRAIT_B
    g++ -c -o obj/debug/TRAIT_B/main.o -DTRAIT_B -frecord-gcc-switches -O0 -g main.cpp
    mkdir -p bin/debug/TRAIT_B
    g++ -DTRAIT_B -frecord-gcc-switches -O0 -g -o bin/debug/TRAIT_B/prog obj/debug/TRAIT_B/main.o

    $ make RELEASE=1 TRAIT_A=1 TRAIT_B=1
    mkdir -p obj/release/TRAIT_A/TRAIT_B
    g++ -c -o obj/release/TRAIT_A/TRAIT_B/main.o -DNDEBUG -DTRAIT_A -DTRAIT_B -frecord-gcc-switches -O3 main.cpp
    mkdir -p bin/release/TRAIT_A/TRAIT_B
    g++ -DNDEBUG -DTRAIT_A -DTRAIT_B -frecord-gcc-switches -O3 -o bin/release/TRAIT_A/TRAIT_B/prog obj/release/TRAIT_A/TRAIT_B/main.o

    $ make RELEASE=1
    g++ -c -o obj/release/main.o -DNDEBUG -frecord-gcc-switches -O3 main.cpp
    g++ -DNDEBUG -frecord-gcc-switches -O3 -o bin/release/prog obj/release/main.o

    最后一个是既没有 TRAIT_A也没有 TRAIT_B的发行版本。

    现在,我们已经在不同的 prog子目录中构建了四个版本的 ./bin[/...]程序
    来自不同 ./obj[/...]子目录中不同目标文件的项目
    这些版本都会告诉我们它们的构建方式不同。按顺序运行
    我们建造了它们:
    $ bin/debug/TRAIT_A/prog

    String dump of section '.GCC.command.line':
    [ 0] -imultiarch x86_64-linux-gnu
    [ 1d] -D_GNU_SOURCE
    [ 2b] -D TRAIT_A
    [ 36] main.cpp
    [ 3f] -mtune=generic
    [ 4e] -march=x86-64
    [ 5c] -auxbase-strip obj/debug/TRAIT_A/main.o
    [ 84] -g
    [ 87] -O0
    [ 8b] -frecord-gcc-switches
    [ a1] -fstack-protector-strong
    [ ba] -Wformat
    [ c3] -Wformat-security

    $ bin/debug/TRAIT_B/prog

    String dump of section '.GCC.command.line':
    [ 0] -imultiarch x86_64-linux-gnu
    [ 1d] -D_GNU_SOURCE
    [ 2b] -D TRAIT_B
    [ 36] main.cpp
    [ 3f] -mtune=generic
    [ 4e] -march=x86-64
    [ 5c] -auxbase-strip obj/debug/TRAIT_B/main.o
    [ 84] -g
    [ 87] -O0
    [ 8b] -frecord-gcc-switches
    [ a1] -fstack-protector-strong
    [ ba] -Wformat
    [ c3] -Wformat-security

    $ bin/release/TRAIT_A/TRAIT_B/prog

    String dump of section '.GCC.command.line':
    [ 0] -imultiarch x86_64-linux-gnu
    [ 1d] -D_GNU_SOURCE
    [ 2b] -D NDEBUG
    [ 35] -D TRAIT_A
    [ 40] -D TRAIT_B
    [ 4b] main.cpp
    [ 54] -mtune=generic
    [ 63] -march=x86-64
    [ 71] -auxbase-strip obj/release/TRAIT_A/TRAIT_B/main.o
    [ a3] -O3
    [ a7] -frecord-gcc-switches
    [ bd] -fstack-protector-strong
    [ d6] -Wformat
    [ df] -Wformat-security

    $ bin/release/prog

    String dump of section '.GCC.command.line':
    [ 0] -imultiarch x86_64-linux-gnu
    [ 1d] -D_GNU_SOURCE
    [ 2b] -D NDEBUG
    [ 35] main.cpp
    [ 3e] -mtune=generic
    [ 4d] -march=x86-64
    [ 5b] -auxbase-strip obj/release/main.o
    [ 7d] -O3
    [ 81] -frecord-gcc-switches
    [ 97] -fstack-protector-strong
    [ b0] -Wformat
    [ b9] -Wformat-security

    我们可以清理第一个:
    $ make DEBUG=1 TRAIT_A=1 clean
    rm -f ./bin/debug/TRAIT_A/prog ./obj/debug/TRAIT_A/main.o

    最后一个:
    $ make RELEASE=1 clean
    rm -f ./bin/release/prog ./obj/release/main.o

    第二和第三仍然存在并且是最新的:
    $ make DEBUG=1 TRAIT_B=1
    make: Nothing to be done for 'all'.

    $ make RELEASE=1 TRAIT_A=1 TRAIT_B=1
    make: Nothing to be done for 'all'.

    在练习中,您可以考虑完善Makefile,以构建或清理
    所有变体同时出现。如果未定义 DEBUG,则默认为 RELEASE,反之亦然。如果未选择有效的特征组合,则失败,否则将导致有效定义。

    顺便说一句,请注意,预处理器选项通常在make变量中分配 CPPFLAGS,用于C或C++编译;分配了C编译器选项
    CFLAGS中使用,在 CXXFLAGS中使用C++编译器选项。 GNU Make的内置
    规则假定您遵守这些约定。

    关于c++ - 基于编译器选项的已编译二进制路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53011443/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com