1.第一个makefile编译C++多文件项目示例

1.1 代码准备

xdata.h

  1. #ifndef XDATA_H
  2. #define XDATA_H
  3. class XData {
  4. public:
  5. XData();
  6. };
  7. #endif

xdata.cpp

  1. #include "xdata.h"
  2. #include <iostream>
  3. using namespace std;
  4. XData::XData()
  5. {
  6. cout << "create xdata" << endl;
  7. }

make_file.cpp

  1. #include "xdata.h"
  2. #include <iostream>
  3. #include <thread>
  4. using namespace std;
  5. void MyThread()
  6. {
  7. cout << "thread main" << endl;
  8. }
  9. int main()
  10. {
  11. thread th(MyThread);
  12. cout << "main hello world" << endl;
  13. th.join();
  14. XData x;
  15. }

1.2 makefile文件编写

  1. first_make: first_make.cpp xdata.cpp
  2. g++ first_make.cpp xdata.cpp -o first_make -lpthread

Makefile 目标规则的一般语法形式:

  1. target [target...] : [dependent ....]
  2. [ command ...]
  • first_make为目标文件
  • first_make.cpp xdata.cpp为目标所依赖的文件
  • g++ first_make.cpp xdata.cpp -o first_make -lpthread中-lpthread是引入的C++11的线程库

    1.3 执行

    在终端中输入:
    1. make
    执行后会看到输出:
    1. g++ first_make.cpp xdata.cpp -o first_make -lpthread

    2.g++分步编译演示从源码到执行程序的四步

3.makefile运行流程分析并使用变量改写项目

3.1 makefile运行流程分析

makefile运行流程分析.svg

3.2 makefile文件主要包含的五部分内容

  • 显式规则。说明了如何生成一个或多个目标文件。由makefile文件的创作者指出,包括要生成的文件、文件的依赖文件、生成的命令。
  • 隐式规则。由于make有自动推导的功能,所以隐式的规则可以比较粗糙地简略书写makefile文件,这是由make所支持的。
  • 变量定义。在makefile文件中要定义一系列的变量,变量一般都是字符串, 这与C语言中的宏有些类似。当makefile文件执行时,其中的变量都会扩展到相应的引用位置上。
  • 文件指示。其包括3个部分,一个是在一个makefile文件中引用另一个 makefile文件;另一个是指根据某些情况指定makefile文件中的有效部分;还有就是定义一个多行的命令。
  • 注释。makefile文件中只有行注释,其注释用“#”字符。如果要在makefile文件中使用“#”字符,可以用反斜框进行转义,如:“#”。

    3.3 变量

    3.3.1 makefile中常见的预定义变量

    作为程序名称的宏(如CC)
命令格式 含义
AR 档案保存程序,默认是ar
AS 汇编程序的名称,默认为as
CC C编译器的名称,默认为cc
CPP C预编译器的名称,默认值为$(CC) -E
CXX C++编译器的名称,默认值为g++
FC FORTRAN编译器的名称,默认值为f77
RM 删除文件的命令,默认是 rm -f

包含程序参数的宏(如CFLAGS)

命令格式 含义
ARFLAGS 库文件维护程序的选项,无默认值
ASFLAGS 汇编程序的选项,无默认值
CFLAGS C编译器的选项,无默认值
CPPFLAGS C预编译器的选项,无默认值
CXXFLAGS C编译器的选项,无默认值
FFLAGS FORTRAN编译器的选项,无默认值

3.3.2 makefile变量的使用

  1. first_make: first_make.cpp xdata.cpp
  2. g++ first_make.cpp xdata.cpp -o first_make -lpthread

将上面的makefile文件改写:

  1. # $^ 依赖 不重复
  2. # $@ 目标
  3. # @ 不显示命令执行 -失败不停止
  4. TARGET=first_make
  5. LIBS=-lpthread
  6. $(TARGET):first_make.cpp xdata.cpp
  7. echo "begin build $(TARGET)"
  8. $(CXX) $^ -o $@ $(LIBS)
  9. echo "$(TARGET) build success!"

执行后会输出:

echo "begin build first_make"
begin build first_make
g++ first_make.cpp xdata.cpp -o first_make -lpthread
echo "first_make build success!"
first_make build success!

若要不输出command,则需要在command前加@符号,如:

TARGET=first_make
LIBS=-lpthread

$(TARGET):first_make.cpp xdata.cpp
    @echo "begin build $(TARGET)"
    @$(CXX) $^ -o $@ $(LIBS)
    @echo "$(TARGET) build success!"

这样执行后输出的内容为:

begin build first_make
first_make build success!

4.makefile自动推导目标代码配置和伪目标clean清理

4.1 makefile自动推导目标代码配置

讲makefile文件中的first_make.cpp xdata.cpp更改为first_make.o xdata.o后执行:

├── first_make
├── first_make.cpp
├── first_make.o
├── makefile
├── xdata.cpp
├── xdata.h
└── xdata.o

会生成.o文件
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
新建头文件 test.h:

//test_gcc
#define CONF_PATH "/usr/local/xcj/"

目录结构为:

.
├── first_make
│   ├── first_make.cpp
│   ├── makefile
│   ├── xdata.cpp
│   └── xdata.h
└── test_gcc
    └── test.h

然后在first_make.cpp文件中导入test.h头文件,最后执行make,会看到如下报错信息:

begin build first_make
first_make.cpp:4:10: fatal error: test.h: No such file or directory
    4 | #include "test.h"
      |          ^~~~~~~~
compilation terminated.
make: *** [makefile:6: first_make] Error

原因是没有找到该文件,故修改makefile文件,增加CXXFLAGS选项,即:

TARGET=first_make
LIBS=-lpthread
OBJS=first_make.o xdata.o

CXXFLAGS=-I../test_gcc

$(TARGET):$(OBJS)
    @echo "begin build $(TARGET)"
    @$(CXX) $^ -o $@ $(LIBS)
    @echo "$(TARGET) build success!"

4.2 伪目标clean清理

在makefile文件中增加:

TARGET=first_make
LIBS=-lpthread
OBJS=first_make.o xdata.o
CXXFLAGS=-I../test_gcc
$(TARGET):$(OBJS)
    @echo "begin build $(TARGET)"
    @$(CXX) $^ -o $@ $(LIBS)
    @echo "$(TARGET) build success!"

clean:
    $(RM) $(OBJS) $(TARGET)
.PHONY: clean *clean

然后 make,生成.o文件和目标文件
最后 make clean 将生成的.o文件和目标文件删除

5.使用make编译动态链接库并编写测试项目

5.1 动态链接库

-fPIC 编译选项
-fPIC if supported for the target machine, emit position-independant code, suitable for dynamic linking, even if branches need large displacements.
产生位置无关代码(PIC),一般创建共享库时用到。
在x86上,PIC的代码的符号引用都是通过ebx进行操作的。
-shared

  • g++ -shared -fPIC mylib.cpp -o libmylib.so
  • g++ test.cpp -lmylib -L/root/cpp
  • !/bin/sh

  • LD_LIBRARY_PATH=./;
  • export LD_LIBRARY_PATH
  • ./testjin’t


5.2 静态链接库

  • 静态库 ar -crv libmylib.a mylib.o
  • [c] - do not warn if the library had to be created 不显示创建
  • [v] - be verbose 显示过程
  • r - replace existing or insert new file(s) into the archive 创建静态库

5.3 示例代码(动态库和静态库)

5.3.1 代码目录层级

.
├── xcom
│   ├── xcom.cpp
│   └── xcom.h
├── xserver
│   ├── makefile
│   └── xserver.cpp
└── xthread
    ├── makefile
    ├── xthread.cpp
    └── xthread.h

5.3.2 xthread目录(动态库)

xthread.h

#ifndef XTHREAD_H
#define XTHREAD_H
#include <thread>

class XThread {
public:
    virtual void start();
    virtual void wait();

private:
    virtual void main() = 0;
    std::thread th_;
};

#endif

xthread.cpp

#include "xthread.h"
#include <iostream>

using namespace std;

void XThread::start()
{
    cout << "start thread." << endl;
    th_ = thread(&XThread::main, this);
}

void XThread::wait()
{
    cout << "begin wait thread" << endl;
    th_.join();
    cout << "end wait thread" << endl;
}

makefile

TARGET=libxthread.so
OBJS=xthread.o
LDFLAGS=-shared
CXXFLAGS=-fPIC
$(TARGET): $(OBJS)
    $(CXX) $(LDFLAGS)  $^ -o $@

clean:
    $(RM) $(TARGET) $(OBJS)
.PHONY: clean

执行 make,生成 libxthread.so 动态库

5.3.3 xserver目录(添加动态库)

xserver.cpp

#include "xthread.h"
#include <iostream>
using namespace std;

class XTask : public XThread {
public:
    void main() override
    {
        cout << "XTask main" << endl;
    }
};
int main(int argc, char* argv[])
{
    cout << "XServer" << endl;
    XTask task;
    task.start();
    task.wait();
    return 0;
}

makefile

TARGET=xserver
OBJS=xserver.o
CXXFLAGS=-I../xthread
LDFLAGS=-L../xthread
LIBS=-lxthread -lpthread
$(TARGET):$(OBJS)
    $(CXX) $^ -o $@ $(LDFLAGS) $(LIBS)

clean:
    $(RM) $(OBJS) $(TARGET)
.PHONY: clean

执行make,生成 xserver 目标文件。
如直接./xserver,则会抛错误,找不到 libxthread.so 动态库

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

则需要在终端设置:

LD_LIBRARY_PATH=./; 
export LD_LIBRARY_PATH

然后运行,输出结果:

XServer
start thread.
begin wait thread
XTask main
end wait thread

5.3.4 xcom目录(静态库)

xcom.h

#ifndef XCOM_H
#define XCOM_H

class XCOM {
public:
    XCOM();
};
#endif

xcom.cpp

#include "xcom.h"
#include <iostream>

using namespace std;

XCOM::XCOM()
{
    cout << "create xcom" << endl;
}

makefile

TARGET=libxcom.a
OBJS=xcom.o
$(TARGET): $(OBJS)
    $(AR) -cvr $@ $^

clean:
    $(RM) $(OBJS) $(TARGET)
.PHONY: clean

在终端执行make即可生成libxcom.a静态库

5.3.5 xserver(添加静态库)

修改xserver.cpp

#include "xcom.h"
#include "xthread.h"
#include <iostream>

using namespace std;

class XTask : public XThread {
public:
    void main() override
    {
        cout << "XTask main" << endl;
    }
};

int main(int argc, char* argv[])
{
    cout << "XServer" << endl;
    XCom xcom;
    XTask task;
    task.start();
    task.wait();
    return 0;
}

修改makefile

TARGET=xserver
OBJS=xserver.o
CXXFLAGS=-I../xthread -I../xcom
LDFLAGS=-L../xthread -L../xcom
LIBS=-lxthread -lpthread -lxcom

$(TARGET):$(OBJS)
    $(CXX) $^ -o $@ $(LDFLAGS) $(LIBS)

clean:
    $(RM) $(OBJS) $(TARGET)
.PHONY: clean

5.3.6 最终的目录结构

.
├── xcom
│   ├── libxcom.a
│   ├── makefile
│   ├── xcom.cpp
│   ├── xcom.h
│   └── xcom.o
├── xserver
│   ├── libxcom.a
│   ├── libxthread.so
│   ├── makefile
│   ├── xserver
│   ├── xserver.cpp
│   └── xserver.o
└── xthread
    ├── libxthread.so
    ├── makefile
    ├── xthread.cpp
    ├── xthread.h
    └── xthread.o

5.3.7 通过ifeq语句实现静态和动态切换

修改xcom目录下的makefile文件

TARGET=libxcom
OBJS=xcom.o

# dynamic library or static library
ifeq ($(STATIC), 1)

# static library
TARGET:=$(TARGET).a
$(TARGET): $(OBJS)
    $(AR) -cvr $@ $^

else

# dynamic library
TARGET:=$(TARGET).so
LDFLAGS=-shared
CXXFLAGS=-fPIC
$(TARGET): $(OBJS)
    $(CXX) $(LDFLAGS) $^ -o $@
endif

clean:
    $(RM) $(OBJS) $(TARGET)
.PHONY: clean *clean

if else endif 中间的指令前不能有缩进

6. makefile函数使用wildcard自动添加目录下源码生成.o

6.1 makefile函数

6.1.1 wildcard

展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表
SRC=$(wildcart .cpp .h)

6.1.2 patsubst

格式:$(patsubst) , , )
名称:模式字符串替换函数 —patsubst
TMP=$(patsubst %.cpp, %.o, $(SRC))

6.2 示例

test.cpp

#include <iostream>
void Test()
{
    std::cout << "Test\n";
}

main.cpp

#include "xdata.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
    extern void Test();
    Test();
    XData d;
    cout << "test make function" << endl;
    return 0;
}

xdata.h

class XData {
public:
    XData();
};

xdata.cpp

#include "xdata.h"
#include <iostream>
XData::XData()
{
    std::cout << "Create XData\n";
}

makefile

TARGET=test_make_func
SRC:=$(wildcard *.cpp)
OBJS=$(patsubst %.cpp,%.o,$(SRC))
$(TARGET):$(OBJS)
    $(CXX) $^ -o $@

执行makefile,终端输出如下:

g++    -c -o xdata.o xdata.cpp
g++    -c -o main.o main.cpp
g++    -c -o test.o test.cpp
g++ xdata.o main.o test.o -o test_make_fun

当前目录文件如下:

.
├── main.cpp
├── main.o
├── makefile
├── test.cpp
├── test_make_fun
├── test.o
├── xdata.cpp
├── xdata.h
└── xdata.o