blogernice

导航

Makefile学习4—打造更专业的编译环境-huge项目

先手工创建几个文件目录:

 

接下来先创建code/foo/src目录下的Makefile:

复制代码
.PHONY: all clean

MKDIR = mkdir
RM = rm
RMFLAGS = -rf

CC =gcc
AR =ar
ARFLAGS =crs

DIR_OBJS=objs
DIR_EXES=../../../build/exes
DIR_DEPS=deps
DIR_LIBS=../../../build/libs
DIRS =$(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS=$(DIR_OBJS) $(DIR_DEPS)

EXE=
ifneq ("$(EXE)","")
EXE :=$(addprefix $(DIR_EXES)/,$(EXE))
RMS +=$(EXE)
endif

LIB=libfoo.a
ifneq ("$(LIB)","")
LIB :=$(addprefix $(DIR_LIBS)/,$(LIB))
RM +=$(LIB)
endif

SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o)
OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS))
DEPS=$(SRCS:.c=.dep)
DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS))

ifeq ("$(wildcard $(DIR_OBJS))","")
DEP_DIR_OBJS :=$(DIR_OBJS)
endif#dir_objs
ifeq ("$(wildcard $(DIR_EXES))","")
DEP_DIR_EXES :=$(DIR_EXES)
endif#dir_exes
ifeq ("$(wildcard $(DIR_DEPS))","")
DEP_DIR_DEPS :=$(DIR_DEPS)
endif#dir_deps
ifeq ("$(wildcard $(DIR_LIBS))","")
DEP_DIR_LIBS :=$(DIR_LIBS)
endif#dir_libs

all: $(EXE) $(LIB)
ifneq ($(MAKECMDGOALS),clean)
include $(DEPS)
endif#clean

$(DIRS):
    $(MKDIR) $@
$(EXE):$(DEP_DIR_EXES) $(OBJS)
    $(CC) -o $@ $(filter %.o,$^)
$(LIB):$(DEP_DIR_LIBS) $(OBJS)
    $(AR) $(ARFLAGS) $@ $(filter %.o,$^)
$(DIR_OBJS)/%.o:$(DEP_DIR_OBJS) %.c 
    $(CC) -o $@ -c $(filter %.c,$^)
$(DIR_DEPS)/%.dep:$(DEP_DIR_DEPS) %.c
    @echo "Creating $@ ..."
    @set -e;\
    $(RM) $(RMFLAGS) $@.tmp;\
    $(CC) -E -MM $(filter %.c,$^) > $@.tmp;\
    sed 's,\(.*\)\.o[:]*,objs/\1.o $@:,g' <$@.tmp >$@;\
    $(RM) $(RMFLAGS) $@.tmp

clean:
    $(RM) $(RMFLAGS) $(RMS)
复制代码

 

具体和complicated项目的差别可以看书或者上篇随笔。

 第一个提示没有那个目录,可以在include的时候加上'-'就可以忽略这个,因为这里对我们的项目没有实质影响,但是新手对于报错或者警告总是不放心,故再次提示一下。

 从运行结果来看,的确在build/libs目录下生成了一个libfoo.a库文件。运行make clean之后,也没有删除build/libs这个目录,而只是删除库文件libfoo.a 

下面要做的就是将这个Makefile运用到code/huge/src目录。

增进复用性:

可以将公用部分放入一个独立的文件中——这就是bulid目录下make.rule文件的作用。这需要我们区别哪些是公用哪些是不能公用的变量。

在考虑复用的情况下,foo模块的Makefile由两部分组成,分别是bulid目录中的make.rule和code/foo/src目录中的Makefile。

foo模块中的Makefile比较简单,因为大部分代码移植到make.rule中:

EXE=
LIB =libfoo.a
include $(ROOT)/build/make.rule

make.rule如下:

复制代码
.PHONY: all clean

MKDIR = mkdir
RM = rm
RMFLAGS = -rf

CC =gcc
AR =ar
ARFLAGS =crs

DIR_OBJS=objs
DIR_EXES=$(ROOT)/build/exes
DIR_DEPS=deps
DIR_LIBS=$(ROOT)/build/libs
DIRS =$(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS=$(DIR_OBJS) $(DIR_DEPS)


ifneq ("$(EXE)","")
EXE :=$(addprefix $(DIR_EXES)/,$(EXE))
RMS +=$(EXE)
endif


ifneq ("$(LIB)","")
LIB :=$(addprefix $(DIR_LIBS)/,$(LIB))
RM +=$(LIB)
endif

SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o)
OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS))
DEPS=$(SRCS:.c=.dep)
DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS))

ifeq ("$(wildcard $(DIR_OBJS))","")
DEP_DIR_OBJS :=$(DIR_OBJS)
endif#dir_objs
ifeq ("$(wildcard $(DIR_EXES))","")
DEP_DIR_EXES :=$(DIR_EXES)
endif#dir_exes
ifeq ("$(wildcard $(DIR_DEPS))","")
DEP_DIR_DEPS :=$(DIR_DEPS)
endif#dir_deps
ifeq ("$(wildcard $(DIR_LIBS))","")
DEP_DIR_LIBS :=$(DIR_LIBS)
endif#dir_libs

all: $(EXE) $(LIB)
ifneq ($(MAKECMDGOALS),clean)
include $(DEPS)
endif#clean

$(DIRS):
    $(MKDIR) $@
$(EXE):$(DEP_DIR_EXES) $(OBJS)
    $(CC) -o $@ $(filter %.o,$^)
$(LIB):$(DEP_DIR_LIBS) $(OBJS)
    $(AR) $(ARFLAGS) $@ $(filter %.o,$^)
$(DIR_OBJS)/%.o:$(DEP_DIR_OBJS) %.c 
    $(CC) -o $@ -c $(filter %.c,$^)
$(DIR_DEPS)/%.dep:$(DEP_DIR_DEPS) %.c
    @echo "Creating $@ ..."
    @set -e;\
    $(RM) $(RMFLAGS) $@.tmp;\
    $(CC) -E -MM $(filter %.c,$^) > $@.tmp;\
    sed 's,\(.*\)\.o[:]*,objs/\1.o $@:,g' <$@.tmp >$@;\
    $(RM) $(RMFLAGS) $@.tmp

clean:
    $(RM) $(RMFLAGS) $(RMS)
复制代码

如果想运行foo模块中的Makefile,先要在shell上导出所需的ROOT变量:

这期间出现了一个问题,我检查了很久Makefile并没有发现问题,但是就是显示没有规则创建目标,最后,用vim重新输入了一下include后面的路径,就解决了,用vscode输入莫名奇妙不行,又没有出现TAB建这样字符,可能是文本编辑器本身的差异吧,但是vim是肯定不会出现格式不兼容问题的。所以以后还是用vim写Makefile吧。

完成上面的之后,我们需要考虑把code/huge/src目录中的Makefile了,我们希望这个目录中存放的程序能生成一个可执行文件。在测试Makefile之前,我们先在该目录中放一个main.c文件,内容如下: 

 int main(void)
 {
     return 0;
 }

Makefile如下所示:

 EXE =huge_app
 
 LIB =
 
 include $(ROOT)/build/make.rule

从结果看huge_app已经成功生成了。

 

 -----------------------------------------------------------------------------------------------------------------------------------------------------------

支持头文件目录指定

在对应目录中放入对应的文件

复制代码
/×    foo.h     */

#ifndef __FOO_H
#define __FOO_H

void foo(void)

#endif/*__FOO_H*/


/*    foo.c     */

#include <stdio.h>
#include "foo.h"
void foo(void)
{
    printf("This is foo()!\n");
}

/*     main.c   */

#include "foo.h"
int main(void)
{
    return 0;
}
复制代码

执行make后报错:

这是在构建依赖文件时,gcc因为找不到foo.h而报错。那是因为foo.h和foo.c放在不同的目录中,这样需要使用gcc的 -I 选项,指定包含路径,所以,更改后的Makefile如下:

复制代码
.PHONY: all clean

MKDIR = mkdir
RM = rm
RMFLAGS =-rf

CC = gcc
AR = ar
ARFLAGS = crs

DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)

ifneq ("$(EXE)", "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif

ifneq ("$(LIB)", "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))

ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
ifeq ("$(wildcard $(DIR_LIBS))", "")
DEP_DIR_LIBS := $(DIR_LIBS)
endif

all: $(EXE) $(LIB)

ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif

ifneq ("$(INCLUDE_DIRS)", "")
INCLUDE_DIRS := $(strip $(INCLUDE_DIRS))
INCLUDE_DIRS := $(addprefix -I, $(INCLUDE_DIRS))
endif

$(DIRS):
    $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS) 
    $(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DEP_DIR_LIBS) $(OBJS)
    $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
    $(CC) $(INCLUDE_DIRS) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
    @echo "Creating $@ ..."
    @set -e ; \
    $(RM) $(RMFLAGS) $@.tmp ; \
    $(CC) $(INCLUDE_DIRS) -E -MM $(filter %.c, $^) > $@.tmp ; \
    sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
    $(RM) $(RMFLAGS) $@.tmp
clean:
    $(RM) $(RMFLAGS) $(RMS)
复制代码

这样就完成了支持头文件目录指定。

从Makefile的角度看,一个可以改善编译效率的地方与其中的隐式规则有关。为了了解make的隐式规则,我们把之前的simple项目的Makefile做一点更改,删除生成.o文件的规则(与隐式规则相对应的是,在Makefile中定义的规则称为显示规则)。

 

复制代码
.PHONY: clean

CC = gcc
RM = rm

EXE =simple
SRCS =$(wildcard *.c)
OBJS =$(patsubst %.c, %.o, $(SRCS))
$(EXE): $(OBJS)
    $(CC) -o $@ $^
#\
%.o : %.c\
    $(CC) -o $@ -c $^删除这两句
clean:
    $(RM) -rf $(EXE) $(OBJS)
复制代码

 

我们make一下,还是成功了,这就是make存在自带的隐式规则的缘故。

在make中存在大量的隐式规则,通过隐式规则将大大简化Makefile的编写。这里简化后的Makefile之所以能工作,是因为make中有着下面这样的隐式规则:

 %.o :%.c
 
   $(CC) -c $(CPPFLAGS) $(CFLAGS)  $^

make需要查找隐式规则会降低编译效率,为了禁止使用make所自带的隐式规则,可以通过make 的 -r选项来实现。

所以,隐式规则是可以被覆盖的。当Makefile中自己定义了生成.o的文件规则时,make就以它为规则,该规则覆盖了make自带的隐式规则。更多隐式规则可以查看GNU MAKE。make -r的使用可以提高每个源文件的编译效率。

恰当的书写注释是个好习惯。

Makefile中的注释以“#”开始,注释多行可以在行末加上"\"。

 

 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

理解make的解析行为

make是以从上到下的顺序读入Makefile中的内容的。然而,处理Makefile中的语句却并非完全从上到下。

大体上,make处理一个Makefile分为两个阶段。第一个阶段包含:

1.make读入Makefile,以及Makefile中所包含的其他Makefile;

2.make分析并获得变量名、变量值、隐式规则和显示规则;

3.构建所有目标的关系树,以及它们的先决条件。

在第二个阶段,make基于第一个阶段所建立的内部结构分析哪些目标需要重新构建,以及需要执行哪些规则的命令来构建这些目标。

理解make处理Makefile的两个阶段对熟练地编写Makefile非常重要。

   ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

gcc常用编译选项

gcc 和 arm-linux-gcc的常用选项

 gcc 的使用方法:

gcc    【选项】    文件名

 gcc常用选项:

  -v:查看gcc 编译器的版本,显示gcc执行时的详细过程

  -o    < file >             Place  the output  into   < file > 

          指定输出文件名为file,这个名称不能跟源文件名同名  

  -E        Preprocess only; do not compile, assemble or link

           只预处理,不会编译、汇编、链接

  -S        Compile only; do not assemble or link

          只编译,不会汇编、链接

  -c        Compile and assemble, but do not link

//=======================================

gcc  -v: 查看 gcc 编译器的版本

 方式1:

gcc  hello.c      输出一个 a.out,然后 ./a.out 来执行该应用程序

 gcc   -o   hello   hello.c     输出hello ,然后 ./hello 来执行该应用程序。

 方式2:

gcc  -E   -o  hello.i    hello.c

gcc  -S   -o  hello.s    hello.i

gcc  -c   -o  hello.o   hello.s

gcc   -o   hello    hello.o

  

.o: object   file (OBJ文件)

小结:

1)输入文件的后缀名和选项共同决定gcc到底执行哪些操作。

 

 

方式3:

gcc  -c   -o  hello.o   hello.s

gcc   -o   hello    hello.o

 

gcc会对.c文件默认进行预处理操作, -c再来指明了编译、汇编,从而得到.o文件,再通过gcc  -o  hello   hello.o   将.o文件进行链接,得到可执行应用程序。

  链接就是将汇编生成的OBJ文件、系统库的OBJ文件、库文件链接起来,最终生成可以在特定平台运行的可执行程序。

 crt1.o、crti.o、crtbegin.o、crtend.o、crtn.o是gcc加入的系统标准启动文件,对于一般应用程序,这些启动是必需的。

 -lc:链接libc库文件,其中libc库文件中就实现了printf等函数。

 

gcc  -v  -nostdlib  -o   hello  hello.o 会提示因为没有链接系统标准启动文件和标准库文件,而链接失败。这个-nostdlib选项常用于裸机 /bootloader 、linux 内核等程序,因为它们不需要启动文件、标准库文件。

 

动态链接使用动态链接库进行链接,生成的程序在执行的时候需要加载所需的动态库才能运行。动态链接生成的程序体积较小,但是必须依赖所需的动态库,否则无法执行。

 静态链接使用静态库进行链接,生成的程序包含程序运行所需要的全部库,可以直接运行,不过静态链接生成的程序比较大。

 ------------------------------------------------------------------------------------------------------------------------------------------------------

动态库和静态库举例说明:

在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o文件。


第1步:编辑得到举例的程序--hello.h、hello.c和main.c;

程序1: hello.h

#ifndef HELLO_H
#define HELLO_H

void hello(const char *name);

#endif //HELLO_H

程序2: hello.c

#include <stdio.h>

void hello(const char *name)
{
printf("Hello %s!\n", name);
}

程序3: main.c

#include "hello.h"

int main()
{
hello("everyone");
return 0;
}

第2步:将hello.c编译成.o文件;

无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件。

在系统提示符下键入以下命令得到hello.o文件。

# gcc -c hello.c

第3步:由.o文件创建静态库;

静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用ar命令。

# ar -crv libmyhello.a hello.o

# ls

hello.c hello.h hello.o libmyhello.a main.c

第4步:在程序中使用静态库;

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。

在程序3:main.c中,我们包含了静态库的头文件hello.h,然后在主程序main中直接调用公用函数hello。下面先生成目标程序hello,然后运行hello程序看看结果如何。

法一 # gcc -o hello main.c -L. –lmyhello

法二 #gcc main.c libmyhello.a -o hello

法三:先生成main.o:gcc -c main.c   ,再生成可执行文件:gcc -o hello   main.o libmyhello.a,动态库连接时也可以这样做。

 

我们删除静态库文件试试公用函数hello是否真的连接到目标文件 hello中了。

# rm libmyhello.a

# ./hello

Hello everyone!

程序照常运行,静态库中的公用函数已经连接到目标文件中了。

第5步:由.o文件创建动态库文件;

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。在系统提示符下键入以下命令得到动态库文件libmyhello.so。

 # gcc -shared -fPCI -o libmyhello.so hello.o (-o不可少)

 # ls

hello.c hello.h hello.o libmyhello.so main.c

第6步:在程序中使用动态库;

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。我们先运行gcc命令生成目标文件,再运行它看看结果。

# gcc -o hello main.c -L. -lmyhello 

 (或 #gcc main.c libmyhello.so -o hello 不会出错(没有libmyhello.so的话,会出错),但是接下来./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到/usr/lib中找库文件的,将文件libmyhello.so复制到目录/usr/lib中就OK了)

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

出错了。快看看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件libmyhello.so复制到目录/usr/lib中,再试试。

# mv libmyhello.so /usr/lib

# ./hello

Hello everyone!

成功了。这也进一步说明了动态库在程序运行时是需要的。

我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样,那当静态库和动态库同名时,gcc命令会使用哪个库文件呢?抱着对问题必究到底的心情,来试试看。

先删除,恢复成我们刚刚编辑完举例程序状态。

# rm -f hello hello.o /usr/lib/libmyhello.so

# ls

hello.c hello.h main.c

再来创建静态库文件libmyhello.a和动态库文件libmyhello.so。

# gcc -c hello.c

# ar -cvr libmyhello.a hello.o 

# gcc -shared -fPCI -o libmyhello.so hello.o

# ls

hello.c hello.h hello.o libmyhello.a libmyhello.so main.c

通过上述最后一条ls命令,可以发现静态库文件libmyhello.a和动态库文件libmyhello.so都已经生成,并都在当前目录中。然后,我们运行gcc命令来使用函数库myhello生成目标文件hello,并运行程序 hello。

# gcc -o hello main.c -L. –lmyhello (动态库和静态库同时存在时,优先使用动态库, 当然,直接#gcc main.c libmyhello.a -o hello的话,就是指定为静态库了)

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

#

从程序hello运行的结果中很容易知道,当静态库和动态库同名时,gcc命令将优先使用动态库,默认去连/usr/lib和/lib等目录中的动态库,将文件libmyhello.so复制到目录/usr/lib中即可。

Note:
编译参数解析
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

-L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在源文件后面就可以了。比如:gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop
其中-L/usr/lib指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如前面的“法二 #gcc main.c libmyhello.a -o hello”)


-lmyhello 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so或.a来确定库的名称libmyhello.so或libmyhello.a。
LD_LIBRARY_PATH 这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。

调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

另:

从上述可知,如何找到生成的动态库有3种方式:

(1)把库拷贝到/usr/lib和/lib目录下。

(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。

例如动态库libhello.so在/home/example/lib目录下:

$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/example/lib

(3) 修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾,并执行ldconfig刷新。这样,加入的目录下的所有库文件都可见。

附:像下面这样指定路径去链接系统的静态库,会报错说要连接的库找不到:

g++ -o main main.cpp -L/usr/lib libpthread.a

必须这样g++ -o main main.cpp -L/usr/lib -lpthread才正确 。

自定义的库考到/usr/lib 下时,

g++ -o main main.cpp   -L/usr/lib libpthread.a libthread.a libclass.a 会出错,但是这样 g++ -o main main.cpp   -L/usr/lib -lpthread -lthread -lclass 就正确了。

 

其它:

1、察看.a里有哪些obj文件
$ar -t libwelcome.a
welcome.o
$ ar -vt libwelcome.a
rw-rw-r-- 507/507     792 Oct 14 08:01 2005 welcome.o
2、.a库文件包含多个obj文件
$ar -r libwelcome.a welcome.o welcome2.o
如果libwelcome.a存在,则libwelcome.a被更新。
如果已存在的libwelcome.a内已经包含welcome.o,则libwelcome.a内welcome.o被更新。
如果已存在的libwelcome.a内没有包含welcome.o,则添加welcome.o到libwelcome.a内,libwelcome.a内原其他obj文件不变。
从libwelcome.a内删除welcome.o
$ ar -d   libwelcome.a   welcome.o

posted on 2019-12-05 20:09  blogernice  阅读(208)  评论(0)    收藏  举报