什么是 redefinition?
redefinition 就是你给同一个东西起了两个或多个名字,编译器会感到困惑,因为它不知道应该使用哪一个。

想象一下你在写一个班级花名册:
// 花名册.c char student_name[100] = "张三"; // ... char student_name[100] = "李四"; // 错误!你不能给同一个条目起两个不同的名字。
编译器就像一个严格的老师,看到这种情况就会立刻报错:error: redefinition of 'student_name'。
常见导致 redefinition 的原因及解决方案
redefinition 主要分为两类:变量的重定义和函数的重定义。
A. 变量的重定义
这是最常见的情况,通常是因为变量在同一个作用域内被定义了多次。

场景 1:在同一个 .c 文件中重复定义
// test.c
#include <stdio.h>
int main() {
int a = 10; // 第一次定义
int a = 20; // 错误!在同一个函数作用域内重复定义了 a
printf("%d\n", a);
return 0;
}
编译错误:
test.c: In function 'main':
test.c:5:5: error: redefinition of 'a'
int a = 20;
^
test.c:4:7: note: previous definition of 'a' with type 'int'
int a = 10;
^
解决方案: 在同一作用域内,一个变量只能被定义一次,如果你想修改变量的值,应该使用赋值,而不是重新定义。
// 正确的做法 int a = 10; a = 20; // 赋值,而不是重新定义
场景 2:在多个 .c 文件中定义全局变量(最经典的多文件重定义问题)
当你把一个项目拆分成多个文件时,这个问题尤其常见。
myheader.h

#ifndef MYHEADER_H #define MYHEADER_H // 声明一个全局变量 g_counter // extern 关键字告诉编译器:“这个变量定义在别的地方,你先别管它的内存,先用这个名字。” extern int g_counter; void increment_counter(); #endif
file1.c
#include "myheader.h"
// 定义全局变量 g_counter
// 这行代码会为 g_counter 分配内存空间。
int g_counter = 0; // 定义
void increment_counter() {
g_counter++;
}
file2.c
#include "myheader.h"
// 错误!这里又对 g_counter 进行了定义!
// 当你编译和链接 file1.c 和 file2.c 时,链接器会发现有两个地方为 g_counter 分配了内存,从而报错。
int g_counter = 100; // 错误的重定义
void print_counter() {
printf("Counter is: %d\n", g_counter);
}
编译错误(由链接器 linker 报出):
/usr/bin/ld: /tmp/ccXXXXXX.o:(.data+0x0): multiple definition of 'g_counter'; /tmp/ccYYYYYYY.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status
解决方案: 遵循“定义一次,声明多次”的原则。
- 定义:只在一个
.c文件中进行。int g_counter = 0;这行就是定义。 - 声明:在所有需要使用这个变量的
.c文件中,通过头文件进行声明。extern int g_counter;这行就是声明。
正确的做法是:
file1.c:保留int g_counter = 0;(定义)。file2.c:删除int g_counter = 100;,只包含myheader.h,通过extern声明来使用它。main.c(假设这是主文件):也包含myheader.h来使用g_counter。
B. 函数的重定义
函数和全局变量一样,也遵循“定义一次,声明多次”的原则。
场景 1:在同一个 .c 文件中重复定义函数
// test.c
#include <stdio.h>
void print_message() {
printf("Hello\n");
}
// 错误!函数 print_message 被定义了两次
void print_message() {
printf("Hi\n");
}
int main() {
print_message();
return 0;
}
编译错误:
test.c:8:6: error: redefinition of 'print_message'
void print_message() {
^
test.c:3:6: note: previous definition of 'print_message' with type 'void(void)'
void print_message() {
^
解决方案: 删除其中一个重复的函数定义。
场景 2:在多个 .c 文件中定义同名函数(最经典的多文件函数重定义问题)
myheader.h
#ifndef MYHEADER_H #define MYHEADER_H // 声明函数 calculate_sum // 函数声明默认就是 extern 的,所以可以省略 extern 关键字 int calculate_sum(int a, int b); #endif
math_utils.c
#include "myheader.h"
// 定义函数 calculate_sum
int calculate_sum(int a, int b) {
return a + b;
}
another_utils.c
#include "myheader.h"
// 错误!这里又对 calculate_sum 进行了定义!
// 链接器会找到两个 calculate_sum 的实现,不知道该调用哪一个。
int calculate_sum(int a, int b) {
return a * b; // 不同的实现
}
编译错误(由链接器 linker 报出):
/usr/bin/ld: /tmp/ccXXXXXX.o:(.text+0x0): multiple definition of 'calculate_sum'; /tmp/ccYYYYYYY.o:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
解决方案:
和全局变量一样,确保函数只在一个 .c 文件中定义,在其他需要它的地方通过头文件声明。
#ifndef / #define / #endif 的作用 (头文件保护)
你可能会问,为什么头文件里要加这三行?它们是为了防止头文件被重复包含,从而间接导致重定义。
假设没有头文件保护:
myheader.h
int g_counter = 0; // 错误:在头文件中定义全局变量! void my_func();
file1.c
#include "myheader.h" // ...
file2.c
#include "myheader.h" // ...
当你编译项目时,file1.c 和 file2.c 都会包含 myheader.h,链接器会看到 g_counter 被定义了两次(一次在 file1.c 的预处理结果中,一次在 file2.c 的预处理结果中),从而导致 redefinition 错误。
#ifndef / #define / #endif 的工作原理:
#ifndef MYHEADER_H:MYHEADER_H这个宏没有被定义...#define MYHEADER_H:...那么就定义它,并执行下面的代码直到#endif。#endif:结束。
这样,无论一个文件包含了多少次 myheader.h,它里面的实际内容只会被处理一次。
最佳实践:
- 全局变量:永远不要在
.h文件中定义(除非使用static关键字,但这会限制其作用域),应该在一个.c文件中定义,在其他文件中用extern声明。 - 函数:在
.h文件中声明,在对应的.c文件中定义。 - 头文件:始终使用
#ifndef/#define/#endif进行保护。
| 错误类型 | 原因 | 解决方案 |
|---|---|---|
| 变量重定义 | 同一作用域内定义多次。 2. 多个 .c 文件中都对同一个全局变量进行了定义。 |
在同一作用域内,一个变量只定义一次,赋值时使用 。 2. 遵循“定义一次,声明多次”原则,在一个 .c 文件中 int x = ...;,在其他文件中 extern int x;。 |
| 函数重定义 | 同一 .c 文件中定义了多个同名函数。 2. 多个 .c 文件中都对同一个函数进行了定义。 |
删除重复的函数定义。 2. 遵循“定义一次,声明多次”原则,在一个 .c 文件中实现函数,在 .h 文件中声明函数。 |
| 头文件重复包含 | 多个文件包含了同一个没有保护的头文件,导致头文件中的内容(如函数声明)被重复处理。 | 在头文件中使用 #ifndef / #define / #endif 宏进行保护。 |
理解 redefinition 的核心是区分“定义”和“声明”,并理解作用域的概念,这是从编写单文件程序过渡到多文件程序的关键一步。
