Linux Makefile : From Basic Syntax to Multi-File Project Compilation

Linux Makefile Beginner's Practical Guide: From Basic Syntax to Multi-File Project Compilation

In Linux C language development, manually executing gcc compilation commands becomes cumbersome and error-prone when a project contains multiple source files. Makefile, as a project management tool under Linux, enables one-click compilation, on-demand building, and flexible configuration of compilation options, making it an essential skill for multi-file project development. This article combines practical code examples to explain the writing logic and practical application of Makefile step by step, from basic syntax to advanced writing styles.

I. Core Basics of Makefile

1.1 Role and Core Commands of Makefile

  • Core Role: Automates compilation process management, specifies source file dependencies and compilation rules to achieve one-click project building; supports compilation of source files in multiple directories, custom compilation parameters, and improves development efficiency.
  • Core Commands :
    • make: Searches for makefile or Makefile (recommended to use uppercase naming) in the current directory and executes compilation according to the rules. If the source files have not been modified, it will prompt 'target file' is up to date. to avoid redundant compilation.
    • make clean: Executes the rules of the clean target in the Makefile, used to clear compiled executable files, intermediate files, etc., to achieve project cleanup.

1.2 Core Writing Rules of Makefile

The core syntax of Makefile follows the format of "target: dependencies" + "TAB execution rule", which is indispensable:

makefile 复制代码
# Format template
target_file: dependency_file_list
	TAB compilation/execution_command (must start with TAB, not spaces)
  • Target : The final file to be generated (such as the executable file a.out) or an operation (such as clean);
  • Dependencies : Files required to generate the target (such as .c source files, .h header files);
  • Execution Rule : The specific command to generate the target (such as the gcc compilation instruction), must start with a TAB key (a high-frequency pitfall in Makefile writing).

II. Project Preparation

Before writing the Makefile, first build a simple multi-file C project that includes header files , main function files , and function implementation files , complying with project development specifications (Note: In the original code, the implementations of add/sub functions are written in main.c, which does not conform to the principle of "separation of declaration and implementation". The standard structure is adjusted below).

2.1 Project File Structure

复制代码
./
├── main.c    # Main function entry, calls add/sub functions
├── func.c    # Function implementations (add/sub)
└── func.h    # Function declarations

2.2 Code Implementation of Each File

1. Header File func.h (Function Declarations)
c 复制代码
#ifndef __FUNC_H_
#define __FUNC_H_

// Function declarations, exposing interfaces to main.c
int add(int a, int b);
int sub(int a, int b);

#endif
2. Function Implementation File func.c (Function Definitions)
c 复制代码
#include "func.h"

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

// Subtraction implementation
int sub(int a, int b)
{
    return a - b;
}
3. Main Function File main.c (Business Logic)
c 复制代码
#include <stdio.h>
#include "func.h"

int main()
{
    int a = 10;
    int b = 20;
    int c = add(a, b);
    printf("add is %d\n", c);
    c = sub(a, b);
    printf("sub is %d\n", c);
    return 0;
}

III. Makefile from Basic to Advanced (Three Versions)

For the above project, we start with the most basic Makefile writing method and gradually optimize it to a more maintainable advanced version.

Version 1: Basic Writing (Hard-Coded Dependencies and Commands)

This is the most basic form of Makefile, directly specifying targets, dependencies, and compilation commands, suitable for beginners to understand the core logic.

makefile 复制代码
# Version 1: Basic Makefile
# Target: a.out; Dependencies: main.c, func.c
a.out: main.c func.c
	gcc main.c func.c -o a.out  # Note: This line must start with TAB
# Clean target: no dependencies, used to delete executable files
clean:
	rm -f a.out  # -f avoids errors when the file does not exist
Execution Effect
  1. First Compilation : Execute make, the Make tool detects that a.out does not exist, and executes the gcc command to generate the executable file:

    bash 复制代码
    linux@ubuntu:~/project$ make
    gcc main.c func.c -o a.out
  2. Redundant Compilation : If the source files are not modified, executing make again will prompt that compilation is unnecessary:

    bash 复制代码
    linux@ubuntu:~/project$ make
    make: 'a.out' is up to date.
  3. Cleaning Files : Execute make clean to delete a.out:

    bash 复制代码
    linux@ubuntu:~/project$ make clean
    rm -f a.out
Advantages and Disadvantages
  • Advantages: Simple logic, easy to understand;
  • Disadvantages: When dependencies or compilation commands change, multiple places need to be modified manually, resulting in poor maintainability.

Version 2: Simplified Writing with Built-in Variables

Makefile provides built-in automatic variables that can replace repeated dependency and target names, simplifying rule writing and improving flexibility.

Common built-in variables:

  • $^: Represents all dependency files in the current rule;
  • $@: Represents the target file in the current rule.

Optimized Makefile based on built-in variables:

makefile 复制代码
# Version 2: Simplified version with built-in variables
a.out: main.c func.c
	gcc $^ -o $@  # $^ replaces main.c func.c, $@ replaces a.out
clean:
	rm -f a.out  # $@ here does not refer to a.out for the clean target, so it is recommended to write the target file name explicitly
Core Analysis
  • gcc $^ -o $@ is equivalent to gcc main.c func.c -o a.out. When dependency files are added (such as adding utils.c), only the dependency list needs to be modified, and the compilation command does not need to be changed;
  • Note: The clean target has no dependencies, and $@ in its rule does not represent an executable file. Therefore, it is recommended to explicitly write the target file name in the clean rule of Version 2 to avoid ambiguity.

Version 3: Custom Variables (Project-Level Writing)

For complex projects, custom variables can be used to uniformly manage source file lists, target file names, and compilation options, achieving "modify once, take effect globally", which is the standard writing method for project development.

makefile 复制代码
# Version 3: Project-level writing with custom variables
# 1. Custom variables: manage source files, target files, and compilation options
SRC = main.c       # Source file list, initially main.c
SRC += func.c      # Append func.c; if new source files are added, append them here directly
APP = a.out        # Executable file name
FLAG = -g          # Compilation option: -g generates debugging information for gdb debugging

# 2. Compilation rule: dependencies are SRC variables, target is APP variable
$(APP): $(SRC)
	gcc $^ -o $@ $(FLAG)  # Add debugging option FLAG

# 3. Clean rule: use APP variable for unified management
clean:
	rm -f $(APP)
Core Advantages
  1. High Maintainability : If new source files are added (such as utils.c), only need to add SRC += utils.c; if the executable file name is modified, only the APP variable needs to be changed;
  2. Flexible Compilation Options : Various compilation parameters can be added through the FLAG variable (such as -Wall to enable warnings, -O2 for optimized compilation, -I to specify header file paths);
  3. Unified Rules: All targets and dependencies are associated through variables, avoiding errors caused by hard coding.
Debugging Function Verification

After adding the -g compilation option, you can use gdb to debug the executable file:

bash 复制代码
# Compile to generate a.out with debugging information
make
# Start gdb debugging
gdb ./a.out

IV. Key Notes for Makefile

  1. Mandatory TAB Indentation : The execution commands in the rules must start with a TAB key . If spaces are used instead, the Make tool will report an error missing separator, which is the most common pitfall for beginners;

  2. On-Demand Compilation Mechanism: The Make tool compares the modification times of target files and dependency files, and only recompiles when the dependency files are updated to avoid invalid operations;

  3. Special Nature of the clean Target : clean is not a target for generating files but a cleanup operation. To avoid conflicts with files of the same name, you can add a .PHONY declaration (advanced optimization):

    makefile 复制代码
    # Declare clean as a phony target to ensure the rule is executed even if a file named clean exists
    .PHONY: clean
    clean:
    	rm -f $(APP)
  4. Header File Dependencies : If header files in the project (such as func.h) are modified, the Makefile will not trigger recompilation by default. You can add header files to the dependencies:

    makefile 复制代码
    $(APP): $(SRC) func.h  # Add func.h as a dependency to automatically recompile when the header file is modified
    	gcc $^ -o $@ $(FLAG)

V. Complete Project Compilation and Running Process

  1. Write Files : Create main.c, func.c, func.h, and Makefile (Version 3) according to the structure above;

  2. One-Click Compilation : Execute make to generate the executable file a.out;

  3. Run the Program : Execute ./a.out, and the output is as follows:

    bash 复制代码
    add is 30
    sub is -10
  4. Clean the Project : Execute make clean to delete a.out and restore the project to its initial state.

VI. Summary and Advanced Learning Directions

6.1 Core Takeaways

  1. The core of Makefile is the "target-dependency-rule" model, and project-level management can be achieved through automatic variables and custom variables;
  2. From basic hard coding to custom variables, Makefile writing methods gradually move towards maintainability and flexibility;
  3. Mastering the core usage of make and make clean can greatly improve the compilation efficiency of multi-file projects.

6.2 Advanced Learning Directions

  1. Multi-Directory Source File Compilation : Manage header files and source files in different directories through vpath or -I/-L parameters;
  2. Static/Dynamic Library Compilation : Implement the construction and linking of .a static libraries and .so dynamic libraries in Makefile;
  3. Cross-Platform Compilation: Achieve compilation rule adaptation for different systems (Linux/Windows) combined with conditional judgments.
相关推荐
末日汐3 小时前
linux线程
linux·运维
天人合一peng3 小时前
Unity中button 和toggle监听事件函数有无参数
前端·unity·游戏引擎
玉梅小洋3 小时前
CentOS :yum源配置及验证指南
linux·运维·centos·yum
zxsz_com_cn3 小时前
设备预测性维护算法核心功能有哪些?六大模块拆解智能运维的“技术骨架”
运维·算法
自可乐3 小时前
n8n全面学习教程:从入门到精通的自动化工作流引擎实践指南
运维·人工智能·学习·自动化
沐芊屿3 小时前
华为交换机配置M-LAG
服务器·网络·华为
枷锁—sha3 小时前
【SRC】越权漏洞检测
运维·服务器·网络·安全·网络安全·系统安全
UP_Continue4 小时前
Linux--进程控制
linux·运维·服务器
请输入蚊子4 小时前
«操作系统真像还原» 第二章 编写MBR主引导记录
linux·汇编·操作系统·bochs·操作系统真像还原
等什么君!4 小时前
docker -数据卷技术
运维·docker·容器