You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Makefile自动生成对象文件时链接器无法找到函数引用问题

链接器错误排查:undefined reference与multiple definition问题

编译生成对象文件后,链接器报错undefined reference to <function>,但相关函数已完成声明与定义,手工编译链接所有对象文件可正常运行,但使用Makefile执行时出现以下错误:

mkdir -p ./build/
clang -g -I./include/ -c src/file1.c -o build/file1.o
clang -g -I./include/ -c src/file1.c -o build/file2.o
clang -g -I./include/ -o a.out main.c ./build/file1.o ./build/file2.o
/usr/bin/ld: ./build/file2.o: in function `some_bit_func':
/home/rat/dev/some_project/src/file1.c:4: multiple definition of `some_bit_func'; ./build/file1.o:/home/rat/dev/some_project/src/file1.c:4: first defined here
/usr/bin/ld: /tmp/main-6cf008.o: in function `main':
/home/rat/dev/some_project/main.c:13: undefined reference to `some_func'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [Makefile:21: a.out] Error 1

问题代码与结构

简化后的Makefile

CC := clang
CFLAGS := -g

BIN := a.out
BUILDDIR := ./build/
SRCDIR := ./src/
INCDIR := ./include/
CFLAGS += -I$(INCDIR)

SRCEXCL := $(SRCDIR)file3.c
INCEXCL := $(INCDIR)file3.h

SOURCES := $(filter-out $(SRCEXCL), $(wildcard $(SRCDIR)*.c))
INCLUDE := $(filter-out $(INCEXCL), $(wildcard $(INCDIR)*.h))
OBJECTS := $(addprefix $(BUILDDIR), $(subst $(SRCDIR),, $(SOURCES:.c=.o)))

.PHONY: all
all: $(BIN) run clean

$(BIN): main.c $(OBJECTS)
        $(CC) $(CFLAGS) -o $@ $< $(OBJECTS)

$(OBJECTS): | $(BUILDDIR)

$(BUILDDIR)%.o: $(SOURCES) $(INCLUDE) build
        $(CC) $(CFLAGS) -c $< -o $@

build:
        mkdir -p $(BUILDDIR)

run:
        ./$(BIN)

clean:
        rm -rf $(BUILDDIR) $(BIN)

项目文件结构

./include/shared.h

#pragma once
#ifndef _DATA_H_
#define _DATA_H_

#define CHECK_BIT(num, pos) ((num) & (pos))

// just some enum
enum bits {
  ONE = 1, TWO = 2, THREE = 4,
  FOUR = 8, FIVE = 16, SIX = 32,
  SEVEN = 64, EIGHT = 128, NINE = 256,
  INVALID = -1
};

#endif    // _DATA_H_

./include/file1.h

#pragma once
#ifndef _FILE1_H_
#define _FILE1_H_

#include <shared.h>

#include <stdint.h>

// just some function
enum bits some_bit_func(uint16_t number);

#endif    // _FILE1_H_

./src/file1.c

#include <file1.h>

enum bits some_bit_func(uint16_t number)
{
  enum bits result;
  // 100% valid code here
  return result;
}

./src/file2.c

#include <shared.h>
#include <stdint.h>

uint16_t some_func(uint16_t number)
{
  return CHECK_BIT(number, FOUR);
}

main.c

#include <file1.h>
#include <stdio.h>
#include <sys/types.h>

extern uint16_t some_func(uint16_t number);

int main(void)
{
  size_t arr_size = 3;
  uint16_t numbers[] = { 5, 9, 7 };

  for (size_t i = 0; i < arr_size; i++) {
    printf("some_func(%d):[%d]: %d\n", numbers[i], i, some_func(numbers[i]));
    printf("some_bit_func(%d):[%d]: %d\n\n", numbers[i], i, some_bit_func(numbers[i]));
  }
  return 0;
}

错误原因分析

从Make的执行输出能直接定位问题:

  1. multiple definition错误:编译build/file2.o时用的是src/file1.c作为源文件,导致file2.o中也包含some_bit_func的定义,和file1.o重复。
  2. undefined reference错误:src/file2.c根本没被编译,所以some_func的目标代码不存在,链接时找不到。

根源在Makefile的目标文件编译规则:

$(BUILDDIR)%.o: $(SOURCES) $(INCLUDE) build
        $(CC) $(CFLAGS) -c $< -o $@
  • 规则的依赖是$(SOURCES)(所有源文件),$<代表第一个依赖,也就是src/file1.c,所以不管是生成哪个.o文件,都是编译src/file1.c。
  • 正确的规则应该让每个$(BUILDDIR)%.o对应$(SRCDIR)%.c,只依赖自身的源文件和头文件。

修复后的Makefile

CC := clang
CFLAGS := -g

BIN := a.out
BUILDDIR := ./build/
SRCDIR := ./src/
INCDIR := ./include/
CFLAGS += -I$(INCDIR)

SRCEXCL := $(SRCDIR)file3.c
INCEXCL := $(INCDIR)file3.h

SOURCES := $(filter-out $(SRCEXCL), $(wildcard $(SRCDIR)*.c))
INCLUDE := $(filter-out $(INCEXCL), $(wildcard $(INCDIR)*.h))
OBJECTS := $(addprefix $(BUILDDIR), $(notdir $(SOURCES:.c=.o)))

.PHONY: all run clean build
all: $(BIN) run clean

$(BIN): main.c $(OBJECTS)
        $(CC) $(CFLAGS) -o $@ $^

$(BUILDDIR)%.o: $(SRCDIR)%.c $(INCLUDE) | $(BUILDDIR)
        $(CC) $(CFLAGS) -c $< -o $@

$(BUILDDIR):
        mkdir -p $@

run:
        ./$(BIN)

clean:
        rm -rf $(BUILDDIR) $(BIN)

关键修改点

  1. 修正目标文件匹配规则:$(BUILDDIR)%.o: $(SRCDIR)%.c $(INCLUDE) | $(BUILDDIR),让每个.o文件对应src目录下同名的.c文件。
  2. 调整OBJECTS生成方式:用$(notdir)替代subst,更简洁地提取源文件名。
  3. 链接命令用$^替代$<:$^会包含所有依赖(main.c + 所有OBJECTS),避免遗漏。
  4. 简化BUILDDIR创建规则:直接用$(BUILDDIR)作为目标,替代单独的build伪目标。

内容的提问来源于stack exchange,提问作者rat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:25:59