Makefile

如何编译 C++ 程序:轻松搞定 Makefile

示例

#设置编译器
CC := g++

SRCS := $(shell find ./* -type f | grep '\.cpp' | grep -v './main\.cpp')
$(warning SRCS is $(SRCS))

OBJS :=$(patsubst %.cpp, %.o,$(filter %.cpp, $(SRCS)))
$(warning OBJS is $(OBJS))

#搜索路径
INCLUDE := -I.

MAIN_SRC :=main.cpp
MAIN_OBJ :=$(patsubst %.cpp,%.o,$(MAIN_SRC))
MAIN_EXE :=main

target:$(MAIN_EXE) 
$(MAIN_EXE):$(OBJS) $(MAIN_OBJ) 
	$(CC)  -o $@ $^ $(INCLUDE) 

%.o: %.cpp
	$(CC) $(INCLUDE) -c $< -o $@

clean:
	rm -rf *.o $(MAIN_EXE) 
  • 代码

main.cpp

#include<iostream>
#include"add.h"
int main()
{
    std::cout<<"result: "<<add(3,5)<<std::endl;
    return 0;
}

add.h

#pragma once
int add(int a,int b);

add.cpp

#include"add.h"
int add(int a,int b)
{
    return a+b;
}

编译一个简单的程序

gcc

编译

源文件(.cpp)编译生成目标文件(.o)

gcc -c main.c -o main.o

链接

目标文件(.o)链接生成可执行文件

gcc main.o -o main

单命令

不生成 .o文件

gcc main.cpp -o main -lstdc++

运行

./main

链接错误

test.o:test.cpp:(.text+0x17): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
test.o:test.cpp:(.text+0x4f): undefined reference to `std::ios_base::Init::~Init()'
test.o:test.cpp:(.text+0x7f): undefined reference to `std::ios_base::Init::Init()'
test.o:test.cpp:(.rdata$.refptr._ZSt4cout[.refptr._ZSt4cout]+0x0): undefined reference to `std::cout'
collect2.exe: error: ld returned 1 exit status
  • 解决方法

链接某个库

gcc main.o -o main -lstdc++

g++

编译

g++ -c main.cpp -o main.o

链接

g++ main.o -o main

单命令

g++ main.cpp -o main

区别:g++相较gcc会默认链接标准库

C++编译流程

  1. 预处理

    把#include语句以及一些宏插入程序文本中,得到*.i文件

    g++ -E main.cpp -o main.i
    
  2. 编译

    将*.i 编译成 *.s的汇编语言程序

    g++ -S main.i -o main.s
    
  3. 汇编

    将* .s翻译成机器语言的二进制指令,并打包成一种可重定位目标程序的格式,并将结果保存在*.o文件中

    g++ -c main.s -o main.o
    
    
  4. 链接

    合并全部 *.o文件,得到可执行文件。

    g++ main.o -o main
    

预处理、编译、汇编三合一

g++ -c main.cpp -o main.o

生成 .o文件时才加-c

预处理、编译、汇编、链接四合一

g++ main.cpp -o main

编译和链接错误

编译错误

为包含头文件

源代码

main.cpp

#include<iostream>

int main()
{
    std::cout<<"result: "<<add(3,5)<<std::end;
    return 0;
}

add.cpp

int add(int a,int b)
{
    return a+b;
}

运行

g++ -c add.cpp -o add.o
g++ -c main.cpp -o main.o
g++ main.o add.o -o main

g++ main.cpp add.cpp -o main

报错

main.cpp: In function 'int main()':
main.cpp:5:28: error: 'add' was not declared in this scope
     std::cout<<"result: "<<add(3,5)<<std::end;
                            ^~~
main.cpp:5:28: note: suggested alternative: 'rand'
     std::cout<<"result: "<<add(3,5)<<std::end;
                            ^~~
                            rand

解决

  • 写头文件add.h
#pragma once
int add(int a,int b);
  • 包含头文件
#include<iostream>
#include"add.h"
int main()
{
    std::cout<<"result: "<<add(3,5)<<std::endl;
    return 0;
}

链接错误

未提供实现

源代码

add.cpp为空

// int add(int a,int b)
// {
//     return a+b;
// }

报错

main.o:main.cpp:(.text+0x34): undefined reference to `add(int, int)'
collect2.exe: error: ld returned 1 exit status

解决

在add.cpp中提供实现(记得保存),重新编译。

源文件要包含头文件

  • 会检查函数定义与函数实现是否一致

头文件搜索路径

  • #include "head.h"按照相对工程的路径去找头文件

  • #include <head.h>按照头文件搜素路径去找头文件

    • 将当前路径.添加到头文件搜索路径
  g++ -I. add.cpp main.cpp -o main

-I dir 将dir添加到文件查找路径

多文件编译

  • 法一

    每个源文件生成.o文件,再链接

  • 法二

    g++ -I. add.cpp sub.cpp main.cpp -o main
    

Makefile基本功能

法一

target:
	g++ -I. add.cpp main.cpp -o main
  • target:目标名称,可以随便命名

  • g++ 前面不是空格,必须是 tab 缩进

  • Makefile 里如果有多个目标,可以通过 make加目标名称来执行指定的目标,如果不指定目标,则默认选择第一个目标执行

    make target
    

法二

target:main

main:
	g++ -I. add.cpp main.cpp -o main

tatget main表示 target 目标依赖 main 目标

  • 未指定目标
  make

默认调用第一个目标(target)的第一个目标(main)

  • 指定目标
make main

法三

target: add_o main_o main

main:
	g++ add.o main.o -o main

add_o:
	g++ -I. -c add.cpp -o add.o

main_o:
	g++ -I. -c main.cpp -o main.o 
  • target的各个目标有顺序,先进行add_o再进行main_o,最后进行main

Makefile升级用法

模式规则

target: add.o main.o main

main:
	g++ add.o main.o -o main

%.o: %.cpp
	g++ -I. -c $< -o $@
  • 说明

    • %.o:%.cpp 所有的.o文件都由相同名称的 .cpp文件编译生成
    • <表示第一个依赖值
    • ^表示所有依赖的挨个值
    • @表示所有目标的挨个值

    “<” ,“@” 被称为自动化变量

变量替换

OBJS = main.o add.o

target: $(OBJS) main

main:
	g++ $(OBJS) -o main

%.o: %.cpp
	g++ -I. -c $< -o $@

说明

  • 引用变量时,加上小括号或大括号,这样可以更安全
  • =是最基本的变量
  • :=是覆盖之前的值
  • ?=是如果没有被赋值过,就赋予等号后面的值
  • +=是添加等号后面的值

清理文件

  • PowerShell

    OBJS = main.o add.o
    
    target: $(OBJS) main
    
    main:
    	g++ $(OBJS) -o main
    
    %.o: %.cpp
    	g++ -I. -c $< -o $@
    
    clean:
    	del $(OBJS) main.exe
    
  • Bash

    OBJS = main.o add.o
    
    target: $(OBJS) main
    
    main:
    	g++ $(OBJS) -o main
    
    %.o: %.cpp
    	g++ -I. -c $< -o $@
    
    clean:
    	rm -rf $(OBJS) main
    

Makefile高级用法

文件过滤

SRCS := $(shell find ./* -type f | grep '\.cpp')
$(warning SRCS is $(SRCS))

OBJS :=$(patsubst %.cpp, %.o,$(filter %.cpp, $(SRCS)))
$(warning OBJS is $(OBJS))

target: $(OBJS) main

main:
	g++ $(OBJS) -o main

%.o: %.cpp
	g++ -I. -c $< -o $@

clean:
	rm -rf *.o main	

编译选项

SRCS := $(shell find ./* -type f | grep '\.cpp')
$(warning SRCS is $(SRCS))

OBJS :=$(patsubst %.cpp, %.o,$(filter %.cpp, $(SRCS)))
$(warning OBJS is $(OBJS))

#编译选项
CFLAGS := -g  -o2 -Wall -Werror -Wno-unused -ldl -std=c++11

#搜索路径
INCLUDE := -I.

target: $(OBJS) main

main:
	g++ $(OBJS) -o main

%.o: %.cpp
	g++ $(INCLUDE) $(CFLAGS) -c $< -o $@

clean:
	rm -rf *.o main	

Makefile 多入口编译

多入口

CC := g++

SRCS := $(shell find ./* -type f | grep '\.cpp' | grep -v './main\.cpp' | grep -v './test\.cpp')
$(warning SRCS is $(SRCS))

OBJS :=$(patsubst %.cpp, %.o,$(filter %.cpp, $(SRCS)))
$(warning OBJS is $(OBJS))

#搜索路径
INCLUDE := -I.

MAIN_SRC :=main.cpp
MAIN_OBJ :=$(patsubst %.cpp,%.o,$(MAIN_SRC))
$(warning $(MAIN_OBJ))
MAIN_EXE :=main

TEST_SRC :=test.cpp
TEST_OBJ :=$(patsubst %.cpp,%.o,$(TEST_SRC))
$(warning $(TEST_OBJ))
TEST_EXE :=test

target:$(MAIN_EXE) $(TEST_EXE)

$(MAIN_EXE):$(OBJS) $(MAIN_OBJ) 
	$(CC)  -o $@ $^ $(INCLUDE) 

$(TEST_EXE):$(OBJS) $(TEST_OBJ) 
	$(CC)  -o $@ $^ $(INCLUDE) 

%.o: %.cpp
	$(CC) $(INCLUDE) -c $< -o $@

clean:
	rm -rf *.o $(MAIN_EXE) $(TEST_EXE)
  • $(patsubst %.cpp,%.o,$(MAIN_SRC))表示将MAIN_SRC中的所有.cpp文件替换为.o的文件赋值给MAIN_OBJ

单入口模板

#设置编译器
CC := g++

SRCS := $(shell find ./* -type f | grep '\.cpp' | grep -v './main\.cpp')
$(warning SRCS is $(SRCS))

OBJS :=$(patsubst %.cpp, %.o,$(filter %.cpp, $(SRCS)))
$(warning OBJS is $(OBJS))

#搜索路径
INCLUDE := -I.

MAIN_SRC :=main.cpp
MAIN_OBJ :=$(patsubst %.cpp,%.o,$(MAIN_SRC))
MAIN_EXE :=main

target:$(MAIN_EXE) 
$(MAIN_EXE):$(OBJS) $(MAIN_OBJ) 
	$(CC)  -o $@ $^ $(INCLUDE) 

%.o: %.cpp
	$(CC) $(INCLUDE) -c $< -o $@

clean:
	rm -rf *.o $(MAIN_EXE) 

inc 、src

  • 结构图
|-inc
|  └add.h
|-src
|  └add.cpp
|-main.cpp
|-Makefile
  • 将 add.cpp 挪到 src 目录下仍然可以正常编译
  • 将 add.h、sub.h 挪到 inc目录下只需修改 INCLUDE 即可
#设置编译器
CC := g++

SRCS := $(shell find ./* -type f | grep '\.cpp' | grep -v './main\.cpp')
$(warning SRCS is $(SRCS))

OBJS :=$(patsubst %.cpp, %.o,$(filter %.cpp, $(SRCS)))
$(warning OBJS is $(OBJS))

#搜索路径
INCLUDE := -I. -I ./inc

MAIN_SRC :=main.cpp
MAIN_OBJ :=$(patsubst %.cpp,%.o,$(MAIN_SRC))
MAIN_EXE :=main

target:$(MAIN_EXE) 
$(MAIN_EXE):$(OBJS) $(MAIN_OBJ) 
	$(CC)  -o $@ $^ $(INCLUDE) 

%.o: %.cpp
	$(CC) $(INCLUDE) -c $< -o $@

clean:
	rm -rf *.o $(MAIN_EXE) 

静态库 动态库

P11 P12

【如何编译 C++ 程序:轻松搞定 Makefile】https://www.bilibili.com/video/BV1dF41117NV?p=11&vd_source=ec4e4974e1b56ed330afdb6c6ead1501