好奇的探索者,理性的思考者,踏实的行动者。
Table of Contents:
binutils 一组用于编译、链接、汇编和其它调试目的的程序
* as
汇编器
* ar t libxxx.a
查看静态库中包含的目标文件
* ar x libxxx.a
将所有.o文件解压
* ar cr libtinyxml.a tinystr.o tinyxml.o
打包成静态库
* ranlib
为静态库文件创建索引,相当于ar命令的s选项
* ld
链接器
* nm
可能是name mapping的缩写,查看可执行程序内部的符号,函数名等信息
* ldd
print shared library dependencies
* objcopy
将原目标文件中的内容复制到新的目标文件中,可以通过不同的命令选项调整目标文件的格式,比如去除某些ELF文件头
* objdump
用于生成反汇编文件,主要依赖objcopy实现
* readelf
解读ELF文件
* file
查看文件类型
使用gcc,程序员可以对编译过程有更多的控制。
程序员可以再编译的任何阶段结束后停止整个编译过程以检查编译器在该阶段输出的信息。
gcc --version
gcc -std=c++20 若不支持c++20则会报不支持
--verbose 打印出编译连接时的详细信息
-E 预编译
-c 只编译,不链接 gcc –o hello hello.o 链接目标文件
-o filename 输出文件名,如果没指定filename,默认为a.out
-lstdc++ 代表gcc要用标准的c++的库完成链接
-g 包含调试信息
-D 后面直接跟宏命(中间不加空格),相当于定义这个宏,默认这个宏的内容是1 例如-DLINUX = #define LINUX 1
-l 链接库文件, 库文件一般放在/lib和/usr/lib和/usr/local/lib,库名字为libxxx.so。当目录下有同名的.a和.so文件时,会优先连接so文件
-L 指定其他库文件所在的目录名,即去哪里找so文件, 需要配合-l一起使用
-I 参数是用来指定头文件目录,/usr/include目录一般是不用指定的,gcc知道去那里找,但是如果头文件不在/usr/include里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude参数了
-O 优化编译后的代码
-w 关闭所有告警信息
-Wall 开启所有告警信息
-Werror 所有告警当error来处理
-Wl 选项告诉编译器将后面的参数传递给链接器。
-M 生成.c文件与头文件依赖关系以用于Makefile,包括系统库的头文件
-MM 生成.c文件与头文件依赖关系以用于Makefile,不包括系统库的头文件
-fuse-ld=bfd
使用bfd链接器而不是默认链接器。
-fuse-ld=gold
使用gold链接器而不是默认链接器。
-fuse-ld=lld
使用LLVM lld链接器而不是默认链接器。
gcc默认只链接c的标准库,并不链接c++标准库,需要添加-lstd++
$gcc –lstdc++ -o hello hello.cpp
对于C语言和C++语言,gcc编译完的目标代码是不同的,最大的区别是C++会修改编译后的变量以及函数各种标号的名称
g++是gcc的c++编译器,如果是编译c++的文件,建议使用g++
so文件的源文件中不需要有main函数,即使有也不会被执行。
编译的时候gcc需要加-fPIC
选项,这可以使gcc产生与位置无关的代码。
连接的时候gcc使用-shared
选项,指示生成一个共享库文件。
共享库文件名要以lib开头,扩展名为.so。
按照共享库的命名惯例,每个共享库有三个文件名:real name、soname和linker name。真正的库文件(而不是符号链接)的名字是real name,包含完整的共享库版本号。
soname是一个符号链接的名字,只包含共享库的主版本号,主版本号一致即可保证库函数的接口一致,因此应用程序的.dynamic段只记录共享库的soname。
# 基础使用
gcc -fPIC -c a.c
gcc -fPIC -c b.c
gcc -shared -Wl -o libmyab.so a.o b.o
# 加soname的使用
gcc -shared -Wl -soname libmyab.so.1 -o libmyab.so.1.0.1 a.o b.o
编译动态库的makefile例子:
.SUFFIXES:.c .o
CC=gcc
SRCS=test.c
EXEC=libtest.so
OBJS=$(SRCS:.c=.o)
start:$(OBJS)
$(CC) -shared -o $(EXEC) $(OBJS)
.c.o:
$(CC) -g -fPIC -o $@ -c $<
clean:
rm -f $(OBJS)
so文件使用方法:
1. 为了让linux能找到so文件的位置,需要在.bash_profile中添加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
或者将so文件放入linux的系统目录/usr/lib/
2. 在c文件中使用so文件,首先需要 #inluce
相关h文件。
3. gcc连接时添加 –L参数指明so文件存放路径,-l参数指明so文件名
4. 为了使我们编写的so文件同时可以被C或者C++调用,我们需要修改一下函数申明部分增加带有__cplusplus的预编译指令,extern "C"{}
一种自动化编译的代码的工具
三要素:target、dependency、command
makefile是一个文本形式的脚本文件,其中包含一些规则告诉make编译哪些文件,怎么样编译以及在什么条件下编译。
makefile规则遵循以下通用形式,且依赖是递归结构的
target:dependency [dependency[…]]
command
command
[…]
注意:每个command第一个字符必须是tab键,而不是空格键,不然make会报错并停止。
Makefile里主要包含了五个东西:变量定义、显式规则、隐晦规则、文件指示和注释。
1. 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
2. 显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
3. 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
4. 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样。
5. 注释。只有行注释,用“#”字符
start:
gcc -o hello hello.c
start:hello.o
gcc -o hello hello.o
@echo '---------------ok---------------'
hello.o:
gcc -o hello.o -c hello.c
clean:
rm -f hello.o
为了简化编辑和维护makefile,可以在makefile中使用变量。
varname=some_text
把变量用括号括起来,前面加$
就可以引用该变量的值, $(varname)
按照惯例makefile的变量都是大写(只是习惯而已,不是必须的)。
CC=gcc
SRCS=hello.c
OBJS=hello.o
# OBJS=$(SRCS:.c=.o) 更简便的写法,意思是将SRCS变量中的.c替换为.o
EXEC=hello
start:hello.o
$(CC) -o $(EXEC) $(OBJS)
@echo '---------------ok---------------'
hello.o:
$(CC) -o $(OBJS) -c $(SRCS)
clean:
rm -f hello.o
%.o : %.c
%
通配符,当想生成x.o文件时发现与%.o : %.c
匹配,则将%替换为x,也就是x.o : x.c,则会执行规则的命令。
表示make定义了一条规则,任何x.o文件都从 x.c编译而来
make定义了一些有用的预定义变量
$@
表示target,即目标文件
$<
表示dependency,即第一个依赖
完整例子:
CC=$(CROSS)clang $(TARGET_FLAGS) -lstdc++
AR=$(CROSS)ar -cr
SRCS=Circle.cpp Edge.cpp Math.cpp NavPath.cpp Path.cpp Point.cpp Polygon.cpp Triangle.cpp
OBJS=$(SRCS:.cpp=.o)
EXEC=libNavPath.a
start: $(OBJS)
$(AR) $(EXEC) $(OBJS)
@echo '----------------ok------------$(CC)'
%.o: %.cpp:
$(CC) -Wall -g -o $@ -c $<
clean:
rm -f $(OBJS)
rm -f libNavPath.a
$(C_OBJS):$(OBJ_PATH)/%.o:%.c
$(C_OBJS) 中有很多文件,假设各种各样的都有 (实际中肯定是你自己定义的,不会乱七八糟)
看看这些文件里面,找出匹配符合 $(OBJ_PATH)/%.o的,其实就是过滤出满足$(OBJ_PATH)/%.o的。
一般来说
$(C_OBJS):$(OBJ_PATH)/%.o:%.c
和规则
$(P_OouDIR)/%.o:%.c
是等价的
但是一般性而言,并不等价,比如$(C_OBJS)还定义了其它文件,比如 ./lib/xxx.o
静态模式规则的好处,是能精确定义,哪个文件,依赖哪个文件。规定了预选范围。
%.o:%.c 则是泛泛而谈,只要匹配就可以。
$(foreach <var>,<list>,<text>)
这个函数的意思是,把参数<list>
中的单词逐一取出放到参数<var>
所指定的变量中,然后再执行< text>
所包含的表达式。每一次<text>
会返回一个字符串,循环过程中,<text>
的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
所以,<var>
最好是一个变量名,<list>
可以是一个表达式,而<text>
中一般会使用<var>
这个参数来依次枚举<list>
中的单词。举个例子:
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中,$(name)
中的单词会被挨个取出,并存到变量“n”中,$(n).o
每次根据$(n)
计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)
的值是a.o b.o c.o d.o
。
在Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:$(wildcard PATTERN...)
。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。
一般我们可以使用$(wildcard *.c)
来获取工作目录下的所有的.c文件列表。复杂一些用法;可以使用$(patsubst %.c,%.o,$(wildcard *.c))
,首先使用“wildcard”函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。这样我们就可以得到在当前目录可生成的.o文件列表。因此在一个目录下可以使用如下内容的Makefile来将工作目录下的所有的.c文件进行编译并最后连接成为一个可执行文件:
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
set -x会在执行每一行 shell 脚本时,把执行的内容输出来。它可以让你看到当前执行的情况,里面涉及的变量也会被替换成实际的值。
set -e会在执行出错时结束程序,就像其他语言中的“抛出异常”一样。(准确说,不是所有出错的时候都会结束程序,见下面的注)
makefile:2: *** missing separator. Stop. make
见附件
在一些开源项目中,会使用 autogen.sh+configure+make 方式进行代码编译。
1. 运行 autogen.sh,生成 configure 脚本;
2. 运行 configure 脚本,检查系统配置;
3. 运行 make 命令,执行代码的编译操作;
4. 运行 make install 命令,安装编译生成的文件。
TARGET = main
OBJ_PATH = objs
PREFIX_BIN =
CC = gcc
CPP = g++ -std=c++17
INCLUDES += -I./ -I../include
LIBS = /usr/lib64/libpthread.so /usr/lib64/librt.so
G = -g
CFLAGS :=-Wall -Wno-unknown-pragmas $(G)
LINKFLAGS = -ldl
SRCDIR =. ./include
C_SRCDIR = $(SRCDIR)
C_SOURCES = $(foreach d,$(C_SRCDIR),$(wildcard $(d)/*.c) )
C_OBJS = $(patsubst %.c, $(OBJ_PATH)/%.o, $(C_SOURCES))
C_DEPEND = $(patsubst %.c, $(OBJ_PATH)/%.d, $(C_SOURCES))
CC_SRCDIR = $(SRCDIR)
CC_SOURCES = $(foreach d,$(CC_SRCDIR),$(wildcard $(d)/*.cc) )
CC_OBJS = $(patsubst %.cc, $(OBJ_PATH)/%.o, $(CC_SOURCES))
CC_DEPEND = $(patsubst %.cc, $(OBJ_PATH)/%.d, $(CC_SOURCES))
CPP_SRCDIR = $(SRCDIR)
CPP_SOURCES = $(foreach d,$(CPP_SRCDIR),$(wildcard $(d)/*.cpp) )
CPP_OBJS = $(patsubst %.cpp, $(OBJ_PATH)/%.o, $(CPP_SOURCES))
CPP_DEPEND = $(patsubst %.cpp, $(OBJ_PATH)/%.d, $(CPP_SOURCES))
default: init compile
$(C_OBJS):$(OBJ_PATH)/%.o:%.c
$(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@
$(CC_OBJS):$(OBJ_PATH)/%.o:%.cc
$(CPP) -c $(CFLAGS) $(INCLUDES) $< -o $@
$(CPP_OBJS):$(OBJ_PATH)/%.o:%.cpp
$(CPP) -c $(CFLAGS) $(INCLUDES) $< -o $@
init:
$(foreach d,$(SRCDIR), mkdir -p $(OBJ_PATH)/$(d);)
test:
@echo "C_SOURCES: $(C_SOURCES)"
@echo "C_OBJS: $(C_OBJS)"
@echo "CPP_SOURCES: $(CPP_SOURCES)"
@echo "CPP_OBJS: $(CPP_OBJS)"
@echo "CC_SOURCES: $(CC_SOURCES)"
@echo "CC_OBJS: $(CC_OBJS)"
compile:$(C_OBJS) $(CC_OBJS) $(CPP_OBJS)
$(CPP) $^ -o $(TARGET) $(LINKFLAGS) $(LIBS)
clean:
rm -rf $(OBJ_PATH)
rm -rf $(TARGET)
cleand:
find ./objs -name *.d | xargs rm -rf
install: $(TARGET)
cp $(TARGET) $(PREFIX_BIN)
uninstall:
rm -f $(PREFIX_BIN)/$(TARGET)
# include $(CPP_DEPEND)的作用是生成.d文件,init是先生成objs目录和源文件的子目录
ifeq ($(MAKECMDGOALS),) # 如果目标为空
include $(CPP_DEPEND) init
endif
# $* 不带后缀的target
# $< target
# $@ depend
# $$xx 变量要用两个$$表示
# $$$$ 进程号
# sed中的变量用 '"包围, 's,\('"$$BASESRC"'\)\.o[ :]*,\objs\/$*.o $@ : ,g'
#
# 以下为作用为生成.d文件,
# .d文件就是声明引用的头文件的依赖关系的,文件内容类似于如下
# objs/./example.o objs/./example.d : example.cpp ThreadPool.h
$(CPP_DEPEND):$(OBJ_PATH)/%.d:%.cpp
BASESRC=`basename $*`;\
set -e; rm -f $@; \
$(CPP) $(INCLUDES) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\('"$$BASESRC"'\)\.o[ :]*,\objs\/$*.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$