gcc/g++选项,将所有目标文件放入单独的目录中

80

我想知道为什么gcc/g++没有一个选项将生成的目标文件放置在指定目录。

比如:

mkdir builddir
mkdir builddir/objdir
cd srcdir

gcc -c file1.c file2.c file3.c **--outdir=**../builddir/objdir

我知道可以通过向编译器提供不同的 -o 选项来实现这一点,例如:

gcc -c file1.c -o ../builddir/objdir/file1.o
gcc -c file2.c -o ../builddir/objdir/file2.o
gcc -c file3.c -o ../builddir/objdir/file3.o

我知道可以通过使用VPATH和vpath指令编写Makefile来简化这个过程。

但在复杂的构建环境中,这是一项繁重的工作。

我也可以使用

gcc -c file1.c file2.c file3.c

但是当我使用这种方法时,我的 srcdir 会在之后充满 .o 垃圾。

因此,我认为具有 --outdir 语义的选项非常有用。

你的意见是什么?

编辑:我们的 Makefile 是以这样的方式编写的,即 .o 文件实际上放置在 builddir/obj 中。但我只是想知道是否可能有更好的方法。

编辑:有几种方法可以将实现所需行为的负担转移到构建系统(例如 Make、CMake 等)。但我认为它们都是 gcc(和其他编译器)的弱点的“变通方法”。


你提到了一个复杂的构建环境,如果你正在使用autotools,你可以在源目录之外进行配置和构建。 - Greg Bacon
不,这对我来说不是一个选项。 我认为自动化工具对于团队中其他人而言太过复杂了,而不是我本人。 选项“--outdir”即使你不是Unix专家也很容易理解。 - anon
3
好的昵称。;-) Autotools 的优点在于一旦编写完成后就不需要太多关注(就像一个良好编写的 Makefile 一样),因此无需训练整个团队来使用它们。 - DevSolar
3
关于Autotools:如果某些东西无法按预期工作,它们真的是一场噩梦。 - anon
2
如果您使用CMake,您就不必直接处理autotools,但仍然可以获得autotools风格的配置/构建的好处。 - Dean Michael
不建议同时使用选项“-c”和“-o”。 - Sandburg
10个回答

88
这是我一个项目的精简版makefile,它编译'src'中的源代码,并将.o文件放置在目录"obj"中。关键部分是使用patsubst()函数-有关详细信息,请参见GNU make手册(实际上是相当不错的读物):
OUT = lib/alib.a
CC = g++
ODIR = obj
SDIR = src
INC = -Iinc

_OBJS = a_chsrc.o a_csv.o a_enc.o a_env.o a_except.o \
        a_date.o a_range.o a_opsys.o
OBJS = $(patsubst %,$(ODIR)/%,$(_OBJS))


$(ODIR)/%.o: $(SDIR)/%.cpp 
    $(CC) -c $(INC) -o $@ $< $(CFLAGS) 

$(OUT): $(OBJS) 
    ar rvs $(OUT) $^

.PHONY: clean

clean:
    rm -f $(ODIR)/*.o $(OUT)

不错。目前我也是以类似的方式来做。(注:我问这个问题是因为我想知道是否有更好的方法) - anon
好的标准解决方案。为了简化,是否可以使用“XDIR”来摆脱目标? - Frank
是的,那就是Expat的东西 - 我早该把它删掉了。 - anon
$(CC) -c $(INC) -o $@ $< $(CFLAGS) 这句话是什么意思? - Sayan Dasgupta
ar rvs $(OUT) $^,这是什么意思? - Sayan Dasgupta

24

是否考虑进入该目录并从其中运行编译:

cd builddir/objdir
gcc ../../srcdir/file1.c ../../srcdir/file2.c ../../srcdir/file3.c

就是这样。gcc将解释形式为#include "path/to/header.h"的包含文件作为从该文件所在目录开始的路径,因此您不需要修改任何内容。


4
了解。但正如你所提到的,我必须更改所有相关的包含文件。这可能会非常麻烦,特别是当你有第三方库时。有一个--outdir选项将非常简单。 - anon
2
不,你不需要改变相对包含路径。你只需要在gcc中添加一个额外的-I命令行选项,指向src目录即可。 - R Samuel Klatchko
3
我再次核对了一下,我之前关于需要新的-I的评论是错误的。至少在gcc(版本3.4.5和4.4.0)中,对于以源文件位置开始的“#include”相对路径,都会进行解释。 - R Samuel Klatchko
@RSamuelKlatchko 哎呀,它不起作用。我刚试了一下:结果是所有与 makefile 在同一目录下的对象文件。 - Hi-Angel
@anon 在使用_make_时,每个目标文件都需要一个_gcc_实例,这实际上是一种性能_提升_。为什么呢?make -j5将在您的4 CPU机器上同时运行五个编译任务。这正是_make_的全部意义所在。 - bobbogo
显示剩余2条评论

19
在你的 Makefile 中,在 gcc 调用后添加以下内容是一个简单而有效的解决方法:
mv *.o ../builddir/objdir

甚至可以在编译完成后进行软清理(可能是递归的),例如:

rm -f *.o
或者
find . -name \*.o -exec rm {} \;

1
如果存在像 bar/foo.cbaz/foo.c 这样的源文件,那么这并不能解决问题,因为这两个目标文件会在当前目录中相互覆盖。 - Frank
1
@dehmann - 这个问题是关于如何清除目标文件,而不是防止gcc覆盖它们。 - Davide
1
Makefile无法获取有关目标文件时间的信息,因此它会编译已经编译过的文件。 - bartekordek

12
你可以使用一个简单的gcc包装器,它将生成必要的-o选项并调用gcc
$ ./gcc-wrap -c file1.c file2.c file3.c --outdir=obj 
gcc -o obj/file1.o -c file1.c
gcc -o obj/file2.o -c file2.c
gcc -o obj/file3.o -c file3.c

这是一个最简单形式的gcc_wrap脚本:

#!/usr/bin/perl -w

use File::Spec;
use File::Basename;
use Getopt::Long;
Getopt::Long::Configure(pass_through);

my $GCC = "gcc";
my $outdir = ".";
GetOptions("outdir=s" => \$outdir)
    or die("Options error");

my @c_files;
while(-f $ARGV[-1]){
    push @c_files, pop @ARGV;
}
die("No input files") if(scalar @c_files == 0);

foreach my $c_file (reverse @c_files){
    my($filename, $c_path, $suffix) = fileparse($c_file, ".c");
    my $o_file = File::Spec->catfile($outdir, "$filename.o");
    my $cmd = "$GCC -o $o_file @ARGV $c_file";
    print STDERR "$cmd\n";
    system($cmd) == 0 or die("Could not execute $cmd: $!");
}

当然,标准的解决方法是使用Makefiles,或者更简单的是使用CMakebakefile,但是你特别要求通过在gcc中添加功能来解决问题,我认为唯一的方法是编写这样的包装器。当然,你也可以打补丁修改gcc源代码以包含新选项,但那可能比较困难。


1
“bakefile” 对我来说很新鲜。非常感谢! - anon

8
我相信你的概念是颠倒的...?!
Makefile 的理念在于只处理自上次构建以来已更新的文件,以减少(重新)编译时间。如果您将多个文件捆绑在一起进行编译,则基本上会破坏这个目的。
您的例子:
gcc -c file1.c file2.c file3.c **--outdir=**../builddir/objdir

您没有提供与此命令行配套的“make”规则;但是,如果任何一个文件已更新,则必须运行此行,并重新编译所有三个文件,这可能根本不必要。它还防止“make”为每个源文件生成单独的编译进程,就像使用“-j”选项进行单独编译时一样(我强烈建议使用该选项)。
我在其他地方写了一个Makefile教程,其中详细介绍了一些额外的细节(例如自动检测源文件而不是将它们硬编码到Makefile中,自动确定包含依赖关系和内联测试)。
您只需要将适当的目录信息添加到 OBJFILES := 行和该教程中的%.o:%.c Makefile 规则中即可获得单独的对象目录。 Neil Butterworth的答案很好地说明了如何添加目录信息。

如果你想按照教程描述使用DEPFILES或者TESTFILES,你需要调整DEPFILES :=TSTFILES :=行以及%.t: %.c Makefile pdclib.a规则。


@DevSolar: 我知道Makefile的意图是只处理更改过的文件,以此提高速度。但如果能够实现类似于"gcc -c $(?) --outdir=../builddir/objdir"的东西,它甚至可以加快得多。这样就不必为每个文件单独启动编译器了。相反,我们可以通过一次调用gcc进行"批量编译"。 - anon
优秀的回答,+1 是因为你回答了“为什么gcc不这样做”,而不是“如何更轻松地实现这一点”。 - pra
@ Vokuhila-Oliba:或者你可以让可执行文件gcc(它只是编译器的前端)调用编译器作为某种服务器进程,该进程生成线程而不是在每次调用时设置新进程。三个观察结果:1)这将有助于已经存在的Makefile,而不需要新的自定义语法;2)我不确定GCC是否已经这样做了;3)无论如何,这将是一个问题,需要提交到GCC邮件列表中,而不是StackOverflow。 - DevSolar

2
我认为告诉pass gcc没有单独的选项来指定对象文件的存放位置,因为它已经有了。它是“-c”,它说明在哪个目录中放置对象。仅具有目录的附加标志必须改变“-c”的含义。 例如:
gcc -c file.c -o /a/b/c/file.o --put-object-in-dir-non-existing-option /a1/a2/a3

您不能将/a/b/c/file.o放在/a1/a2/a3下,因为两个路径都是绝对的。因此,“-c”应该改为仅命名对象文件。
我建议您考虑替换makefile,例如cmakescons等。这将使您能够实现适用于简单项目和更大项目的构建系统。
例如,看看使用cmake编译您的示例有多容易。 只需在srcdir/中创建CMakeList.txt文件:
cmake_minimum_required(VERSION 2.6)
project(test) 

add_library(test file1.c file2c file3.c) 

现在输入:

mkdir -p builddir/objdir
cd builddir/objdir
cmake ../../srcdir
make

这是英语翻译成中文的结果:

好了,目标文件将存储在builddir / objdir下的某个位置。

我个人使用cmake,发现它非常方便。它会自动生成依赖项并具有其他好处。


“-c”表示放置对象的意思是什么?不是这样的。“-c”只是表示:“我想要你‘编译’(c代表编译)”。它是一个布尔开关,要么你使用“-c”进行编译,要么不使用“-c”进行编译。而“-o”指的是输出文件(o代表输出)。这不是一个布尔开关,而是一个键值对,因此它是“-o <file>”(作为2个参数)。我认为对原始问题的回答更确切的说法是:你不能。gcc没有这个选项。所有这些答案都是使用其他工具(make、CMake等)的解决方法,以使gcc执行此操作。 - Rich Jahn

2
同时我找到了一个“折中”的解决方案,通过使用 -combine 选项实现。
示例:
mkdir builddir
mkdir builddir/objdir
cd srcdir

gcc -combine -c file1.c file2.c file3.c -o ../builddir/objdir/all-in-one.o

这个操作将所有的源文件合并成一个单一的目标文件。

然而,这只是“半途而废”,因为当只有一个源文件更改时,它仍然需要重新编译所有内容。


2
自gcc 4.5(2010年)起,此选项已被弃用。 - MikeW

2

我也在尝试解决同样的问题。对我来说,这个方法有效:

最初的回答

CC = g++
CFLAGS = -g -Wall -Iinclude
CV4LIBS = `pkg-config --libs opencv4`
CV4FLAGS = `pkg-config --cflags opencv4`

default: track

track:  main.o
    $(CC) -o track $(CV4LIBS) ./obj/main.o

ALLFLAGS = $(CFLAGS) $(CV4FLAGS)
main.o: ./src/main.cpp ./include/main.hpp
    $(CC) $(ALLFLAGS) -c ./src/main.cpp $(CV4LIBS) -o ./obj/main.o
``

1

这是autoconf解决的问题之一。

如果你曾经执行过./configure && make,那么你就知道autoconf是什么:它是生成那些漂亮的configure脚本的工具。但并不是每个人都知道,你可以使用mkdir mybuild && cd mybuild && ../configure && make来代替,这样会神奇地工作,因为autoconf非常棒。

configure脚本在构建目录中生成Makefiles。然后整个构建过程就在那里进行。所以所有的构建文件自然出现在那里,而不是源代码树中。

如果你有源文件,其中包含#include "../banana/peel.h",而且你不能更改它们,那么让它正常工作就很麻烦(你必须将所有头文件复制或链接到构建目录中)。如果你可以将源文件更改为#include "libfood/comedy/banana/peel.h",那么你就万事大吉了。

autoconf并不是特别容易,尤其是对于一个大型的现有项目。但它也有它的优点。


你可以与bakefile一起使用,只需在XML文件中指定要求,它就会为您生成所有内容。 - Frank
抱歉,评论中已经提到了自动工具。嗯,好吧! - Jason Orendorff

-1

个人处理单个文件的时候,我会这样做:

rm -rf temps; mkdir temps; cd temps/ ; gcc -Wall -v --save-temps  ../thisfile.c ; cd ../ ; geany thisfile.c temps/thisfile.s temps/thisfile.i

temps文件夹将保存所有的对象、预处理和汇编文件。

这是一种粗糙的做法,我更喜欢使用Makefiles的上述答案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接