【问题标题】:Separate compilation of interdependent C modules相互依赖的C模块的单独编译
【发布时间】:2013-09-24 22:52:33
【问题描述】:

假设我们有一组相互依赖的 C 模块,并且我们想要创建一个 GNU Makefile,以便为几个不同的构建(例如,单元测试、用户工具、多个版本)单独编译它们。

每个模块虽然对于完整的应用程序至关重要,但旨在单独使用或与其他模块以任何合理的方式组合使用——始终公开功能最强大的 API,这是由为特定构建选择的其他模块提供的组件的可用性而产生的.

为了一个最小且完整的示例,假设我们的程序具有三个模块(红色、绿色和蓝色),所有可能的条件功能都通过条件编译进行切换。每个模块都有两个这样的条件块,每一个都由两个可能的邻居之一启用。这为我们提供了三种可能的单一构建(红色、绿色、蓝色)、三个双重构建(青色、洋红色、黄色)和一个三重构建(白色)——每个构建都包含一个专用的主程序(核心),构建在一组 profided 之上特征。

期望的情况

图 1 显示了三个模块(mod_red.cmod_green.cmod_blue.c «RGB»);跨模块功能的三个区域(青色、洋红色和黄色«CMY»)在相邻模块实现;和三个核心(白色,在大的锐化顶部具有物理依赖性«RGB»,在小顶部具有逻辑依赖性«CMY»)。每个方向(六个方向)都表示一个功能方面,因此指向主三角形的 CMY 顶部表明协同作用可能会提供额外的功能。

所需的 Makefile 应该为所有可能的构建提供配方,因此使用三个模块和七个不同内核的四个版本。它还应该足够聪明以避免残酷的解决方案(每个配方的完整块 gcc 命令)并保持单独编译的优势。

如果没有单独编译,问题很容易(至少对于单边依赖项):主程序包含必要的源,并且依赖块由预处理器标志启用,例如由其他模块设置的包括警卫。但是,通过单独编译,编译器不知道包含特定构建的模块集。

手动方法

可以使用下面列出的 shell 命令手动实现所需的情况。

# Single objects:
gcc -c -o mod_green.o mod_green.c

# Double objects
gcc -c -include mod_blue.h -o mod_red+B.o mod_red.c
gcc -c -include mod_red.h -o mod_blue+R.o mod_blue.c

# Triple objects
gcc -c -include mod_green.h -include mod_blue.h -o mod_red+G+B.o mod_red.c
gcc -c -include mod_red.h -include mod_blue.h -o mod_green+R+B.o mod_green.c
gcc -c -include mod_red.h -include mod_green.h -o mod_blue+R+G.o mod_blue.c

# Builds
gcc -o green green.c mod_green.o
gcc -o magenta magenta.c mod_red+B.o mod_blue+R.o
gcc -o white white.c mod_red+G+B.o mod_green+R+B.o mod_blue+R+G.o

至于所需的情况,此示例仅显示了三个具有代表性的构建:绿色、洋红色和白色。其他类似形成。

经典方法

使用经典的 Makefile 解决方案,Green 构建保持不变,但其他两个缺少逻辑依赖项(即 CMY 提供的符号)。之所以如此,是因为构建过程当前(并且通常)定义如下:

white: white.c mod_red.o mod_green.o mod_blue.o
    gcc -o $@ $^

magenta: magenta.c mod_blue.o mod_red.o 
    gcc -o $@ $^

green: green.c mod_green.o
    gcc -o $@ $^

%.o: %.c
    gcc -c -o $@ $<

这里的问题很明显:最后一条规则没有区分特定的构建——上下文丢失了。此外,我需要为每个模块提供不同的二进制版本以满足不同的构建。正确的做法是什么?

【问题讨论】:

  • 也许我的术语已经过时了,但我发现这个问题不清楚。如果您不使用 Make,您将如何构建 Blue-red?你会使用什么序列的gcc 命令?
  • @Beta 你说得对,理想情况和当前情况之间没有明显的区别—​​—我会改变它。至于gcc,我用~:gcc -c -include mod_blue.h -o mod_red.o mod_red.c,然后mod_blue.o反之,最后第三次调用编译链接项目。
  • 那么您实际上需要 13 个不同的目标文件?
  • @Beta 对于所有情况,我需要其中的 9 个(每个 RGB 模块 3 个,CMY 功能在三个模块中实现)。然后,将主程序作为源文件传递给gcc
  • 这毫无意义。有四种不同的版本涉及 Red 模块:R、RG、RB、RGB。哪两个使用相同的 Red 对象文件?

标签: c gcc compilation makefile


【解决方案1】:

这应该可行。不过没试过。我认为从这一点开始创建其他规则会很容易。

BINARY = build

CC = gcc

SOURCES_RGB = rgb.c mod_red.c mod_green.c mod_blue.c
OBJECTS_RGB = $(SOURCES_RGB:.c=_rgb.o)

BINARY_RGB = $(addprefix RGB-,$(BINARY))

CFLAGS_RGB = -include mod_rgb.h

$(BINARY_RGB): $(OBJECTS_RGB)
        $(CC) -o $@ $^

%_rgb.o: %.c
        $(CC) -c $(CFLAGS_RGB) -o $@ $<

【讨论】:

  • 据我了解,我的想法是我必须为每个构建提供一个头文件,并使用它来编译该构建所需的每个模块。对? (+1)
  • 不,我只是从您的命令行示例中获取了“-include mod_rgb.h”。这个想法是声明链接到每个模块的源,并设置目标来构建对象并将它们链接到模块中。
  • 终于找到了解决办法!它最终得到了我有史以来最复杂的 Makefile,其中一部分在我的答案中给出。我希望它值得一票;-)
【解决方案2】:

在 GNU Make 3.82 中,define 罐装食谱成为可能 - 在被制成参数后 - 可以用作双/三对象构建的模板规格。然后可以使用 Make 独特的 $(call var,par,...) 函数对模板进行实例化,并使用非常特殊的 $(eval code) 进行评估,自 3.8 版起可用。这个技巧可以让我们避免大量样板代码,并使 Makefile 能够自动适应即将到来的项目增长。

虽然拥有所有可能的模块构建规则,但我们离目标仅一步之遥;然而,这是一个相当棘手的步骤。我们需要重新构建每个构建的 先决条件 以显式公开每个模块间依赖项。这是通过用环境邻域产生的特殊情况替换单个模块对象来完成的(例如,用显式mod_red+G.o mod_green+R.o 替换表面的mod_red.o mod_green.o)。替换由包含每个先决条件列表的discover 宏处理,并由指定交叉依赖关系的全局变量xdepend 驱动——实际上,只有少数模块会相互依赖。

# Cross dependency specification (dependent+dependency)
xdepend := blue+red red+blue

# Test for string equality and set membership
equals = $(if $(subst $1,,$2),$(empty),true)
exists = $(if $(filter $1,$2),true,$(empty))

# Extract of the first and second member of a dependency token
pred = $(firstword $(subst +, ,$1))
succ = $(lastword $(subst +, ,$1))

# Rebuild prerequisites to expose modules' interdependencies
discover = $(strip $(foreach mod,$(basename $1),\
    $(subst $(space),,$(obj)/$(mod)\
    $(foreach dep,$(xdepend),\
        $(and\
            $(call equals,$(call pred,$(dep)),$(mod)),\
            $(call exists,$(call succ,$(dep)),$(basename $1)),\
            $(lnk)$(call succ,$(dep))\
        )\
    ).o)\
))


# Create compilation rules for interdependent module objects
define rule
$(obj)/$1$(lnk)$2.o: $(src)/$1.c $(inc)/$1.h $(inc)/$2.h | $(obj)
    $(CC) $(CFLAGS) -include $(inc)/$2.h -c $(src)/$1.c -o $(obj)/$1$(lnk)$2.o

endef

$(foreach dep,$(xdepend),\
    $(eval $(call rule,$(call pred,$(dep)),$(call succ,$(dep))))\
)

有了以上定义,我们现在可以通过以下方式构建我们的项目:

# Rules for Magenta Build and intermediate objects
magenta: $(call discover, magenta.o mod_red.o mod_blue.o)
    $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

$(obj)/%.o: $(src)/%.c $(inc)/%.h | $(obj)
    $(CC) $(CFLAGS) -c -o $@ $<

$(obj):
    mkdir $(obj)

如需进一步说明和最新知识,请阅读GNU Make Manual

【讨论】:

    猜你喜欢
    • 2013-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-26
    • 2021-10-17
    • 1970-01-01
    相关资源
    最近更新 更多