使用Makefile
C 工程结构
一个典型的C工程(lib库)的工程结构可参考如下:
c-project
├── LECENCE
├── Makefile
├── README.md
├── bin
├── build
├── src
│ └── dbg.h
└── tests
bin
:生成的可执行文件目录build
:库文件(.a
)存放目录,编译过程中产生src
: 存放源码test
: 存放单元测试文件
使用Makefile
当一个工程很大,有很多文件时,使用gcc编译就局限了。这个时候通常使用makefile,makefile中,需要把这些文件组织到一起。makefile是一个纯文本文件,本质是一个shell脚本(关于Shell脚本可以参考这里,符号对大小写敏感,下面是一个简单的Makefile文件:
CC=gcc
CFLAGS=-Wall -g
LDFLAGS=
SOURCES=main.cpp hello.cpp factorial.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=hello
all: $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) -o $@
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
- CC: 编译器名称
% gcc main.c
在makefile中的写法为:$(CC) main.c
- CFLAGS: 编译参数,也就是上面提到的gcc的编译选项。
CFLAGS=-Wall -g
- 通常用来指定头文件的位置,常用的是
-I, -g
。
- LDFLAGS: 链接参数,告诉链接器lib的位置
- 常用的有
-I,-L
- 常用的有
- SOURCES: 所有
.c
文件路径 - OBJECTS: 所有
.o
文件路径 - EXECUTABLE: 编译后的可执行文件名称
- action:
all
,clean
为action,可以用make action
来执行某个action- 如果不指定action,默认执行Makefile中的第一个action
Mekfile的工作原理
当执行make
时,编译器会在当前目录下寻找Makefile,找到之后从第一个TARGET开始解析其依赖的变量或其他条件。Makefile的一条Rule的格式为
target: prerequisites
recipe
…
…
- target有两个含义
- 要build的文件名+后缀
- 某个action(比如上面提到的
clean
)
prerequisites
为target的依赖,包含:- 在执行target前需要执行的action
- 在
recipe
中需要的参数
recipe
则为这个action具体执行的shell命令,每条命令单独一行,前面用tab空格
myprogam: main.o foo.o #编译myprogam文件,依赖main.o,foo.o两个文件
gcc -o myprogram main.o foo.o #shell命令
main.o: main.c foo.h #产生main.o 需要main.c, foo.h
gcc -c main.c
foo.o: foo.c foo.h
gcc -c foo.c
A Makefile Demo
CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG $(OPTFLAGS)
LIBS=-ldl $(OPTLIBS)
PREFIX?=/usr/local
SOURCES=$(wildcard src/**/*.c src/*.c)
OBJECTS=$(patsubst %.c,%.o,$(SOURCES))
TEST_SRC=$(wildcard tests/*_tests.c)
TESTS=$(patsubst %.c,%,$(TEST_SRC))
TARGET=build/libYOUR_LIBRARY.a
SO_TARGET=$(patsubst %.a,%.so,$(TARGET))
# The Target Build
all: $(TARGET) $(SO_TARGET) tests
dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
dev: all
$(TARGET): CFLAGS += -fPIC
$(TARGET): build $(OBJECTS)
ar rcs $@ $(OBJECTS)
ranlib $@
$(SO_TARGET): $(TARGET) $(OBJECTS)
$(CC) -shared -o $@ $(OBJECTS)
build:
@mkdir -p build
@mkdir -p bin
# The Unit Tests
.PHONY: tests
tests: CFLAGS += $(TARGET)
tests: $(TESTS)
sh ./tests/runtests.sh
# The Cleaner
clean:
rm -rf build $(OBJECTS) $(TESTS)
rm -f tests/tests.log
find . -name "*.gc*" -exec rm {} \;
rm -rf `find . -name "*.dSYM" -print`
# The Install
install: all
install -d $(DESTDIR)/$(PREFIX)/lib/
install $(TARGET) $(DESTDIR)/$(PREFIX)/lib/
# The Checker
check:
@echo Files with potentially dangerous functions.
@egrep '[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)\
|stpn?cpy|a?sn?printf|byte_)' $(SOURCES) || true
上面的Makefile是个人使用的版本,不同开发人员可能会维护各自的版本,结构大同小异
SOURCES
:GNU Make中提供了一个函数叫wildcard
,它可以将所有符合由其参数描述的文件展开,以空格间隔。因此可以用这个函数展开src
路径下所有的.c
文件OBJECTS
: 使用了另一个GNU Make中的函数patsubst
,它需要三个参数:- 第一个是一个需要匹配的式样;第二个表示用什么来替换它,第三个是一个需要被处理的由空格分隔的列表
- 使用
patsubst
可以方便的生成.o
文件列表
$(OPTFLAGS),$(OPTLIBS)
为执行make命令时的附加参数make OPTFLAGS=-pthread
- 如果附加参数和Makefile中某项option崇明,则会覆盖掉Makefile中该option的值
make PREFIX=/tmp install #installs the lib to /tmp
TARGET/SO_TARGET
: 编译后库文件目录all
: 默认的make命令,依次执行$(TARGET) $(SO_TARGET) tests
这三条rule$(TARGET)
: 具体的编译指令,依赖build和$(OBJECT)
,前者创建文件夹,后者为shell命令参数
C 程序调试
现代可视化的编辑器如Visual Studio, XCode集成了很方便好用的调试工具,使用这些工具可以极大的提升效率,不必纠结于命令行+GDB/LLDB的方式
GDB & LLDB
- GDB tricks
gdb --args [PROGRAM]
Normally, gdb takes arguments you give it and assumes they are for itself. Using –args passes them to the program.thread apply all bt
.Dump a backtrace for all threads. It’s very useful.gdb --batch --ex run --ex bt --ex q --args [PROGRAM] [ARGS]
Run the program so that if it bombs, you get a backtrace.
- GDB参考
run [args]
Start your program with[args]
.break [file:]
function Set a break point at [file:]function. You can also use b.backtrace
Dump a backtrace of the current calling stack. Shorthand isbt
.print expr
Print the value of expr. Shorthand isp
.continue
Continue running the program. Shorthand isc
.next
Next line, but step over function calls. Shorthand isn
.step
Next line, but step into function calls. Shorthand iss
.quit
Exit GDB.help
List the types of commands. You can then get help on the class of command as well as the command.cd
,pwd
,make
just like running these commands in your shell.shell
Quickly start a shell so you can do other things.clear
Clear a breakpoint.info break
,info watch
Show information about breakpoints and watchpoints.attach pid
Attach to a running process so you can debug it.detach
Detach from the process.list
List out the next ten source lines. Add a to list the previous ten lines.
- LLDB参考
run [args]
Start your program with [args].breakpoint set --name [file:]function
Set a break point at [file:]function. You can also useb
, which is way easier.thread backtrace
Dump a backtrace of the current calling stack. Shorthand isbt
.print expr
Print the value of expr. Shorthand isp
.continue
Continue running the program. Shorthand isc
.next
Next line, but step over function calls. Shorthand isn
.step
Next line, but step into function calls. Shorthand iss
.quit
Exit LLDB.help
List the types of commands. You can then get help on the class of command as well as the command itself.cd
,pwd
,make
just like running these commands in your shell.shell
Quickly start a shell so you can do other things.
使用GDB调试需要有debug symbol,GCC编译时加入
-g
生成符号文件(例如XCode会生成 .dSYM文件)