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.
相关推荐
dly_blog2 小时前
Vue 逻辑复用的多种方案对比!
前端·javascript·vue.js
万少2 小时前
HarmonyOS6 接入分享,原来也是三分钟的事情
前端·harmonyos
烛阴2 小时前
C# 正则表达式:量词与锚点——从“.*”到精确匹配
前端·正则表达式·c#
Predestination王瀞潞2 小时前
JDK安装及环境变量配置
java·linux·开发语言
LF3_2 小时前
配置ssh免密登录
运维·ssh
再睡一夏就好2 小时前
深入Linux线程:从轻量级进程到双TCB架构
linux·运维·服务器·c++·学习·架构·线程
wyzqhhhh2 小时前
京东啊啊啊啊啊
开发语言·前端·javascript
JIngJaneIL2 小时前
基于java+ vue助农电商系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端