什么是 "Redefinition" 错误?
"Redefinition" 错误,全称通常是 error: redefinition of '...',意思是同一个标识符(如变量、函数、类型等)在同一个作用域内被定义了两次或更多次。

C 语言的编译器在编译时,需要确保每个实体(entity)在它的作用域内是唯一的,如果你试图重复定义它,编译器就会报错,因为它不知道应该使用哪一个定义。
错误的常见原因及示例
变量重定义
这是最常见的情况,通常发生在头文件被多次包含时。
场景: 你在一个头文件(my_header.h)中定义了一个全局变量,然后在多个 .c 文件中包含了这个头文件。
错误示例代码:

my_header.h
// 错误:这是一个定义,不是声明 int global_counter = 0;
main.c
#include "my_header.h"
#include "my_header.h" // 重复包含,虽然通常编译器会处理,但问题根源在这里
void function_a() {
global_counter++;
}
int main() {
function_a();
return 0;
}
编译错误信息:
In file included from main.c:1:
my_header.h:2:5: error: redefinition of 'global_counter'
int global_counter = 0;
^
main.c:1:1: note: previous definition of 'global_counter' was here
#include "my_header.h"
^
为什么错了?

main.c第一次#include "my_header.h"时,编译器看到int global_counter = 0;,就在全局数据区为global_counter分配了空间并初始化为 0。main.c第二次#include "my_header.h"时,编译器又看到了int global_counter = 0;,它发现,在当前的编译单元(一个.c文件及其包含的所有头文件)中,global_counter已经被定义过了,它就报了 "redefinition" 错误。
如何修正:头文件中的声明与定义分离
这是 C 语言编程的核心原则之一。
- 声明: 告诉编译器“某个东西存在”,比如它的名字、类型是什么。不分配内存空间。
extern int global_counter;(extern关键字表示这是一个声明,定义在其他地方)
- 定义: 告诉编译器“这个东西存在,并且在这里给它分配内存空间”。
int global_counter = 0;
正确做法:
my_header.h
// 正确:只提供声明,告诉其他文件“有一个全局变量叫 global_counter” extern int global_counter;
my_source.c (新建一个 .c 文件来存放定义)
// 正确:提供唯一的定义,注意,这里不需要 extern。 int global_counter = 0;
main.c
#include "my_header.h"
void function_a() {
global_counter++;
}
int main() {
function_a();
return 0;
}
工作原理:
main.c包含my_header.h,编译器知道global_counter是一个int类型的全局变量,但它的大小和地址在main.c中是未知的。my_source.c文件被单独编译,编译器为global_counter分配了内存空间。- 在链接阶段,链接器会将所有编译好的目标文件(
.o文件)合并在一起,它会把main.c中对global_counter的引用与my_source.c中global_counter的实际定义连接起来。
函数重定义
与变量类似,函数也不能在同一个程序中被定义两次。
错误示例代码:
math_utils.h
// 错误:函数定义不应该放在头文件中
int add(int a, int b) {
return a + b;
}
main.c
#include "math_utils.h"
#include "math_utils.h" // 重复包含
int main() {
int sum = add(1, 2);
return 0;
}
编译错误信息:
In file included from main.c:1:
math_utils.h:2:5: error: redefinition of 'add'
int add(int a, int b) {
^
main.c:1:1: note: previous definition of 'add' was here
#include "math_utils.h"
^
为什么错了?
和变量重定义的原因完全一样,头文件被多次包含,导致 add 函数的定义被重复处理。
如何修正:头文件中只放函数声明
math_utils.h
// 正确:只提供函数声明 int add(int a, int b);
math_utils.c
// 正确:提供函数的唯一定义
int add(int a, int b) {
return a + b;
}
main.c
#include "math_utils.h"
int main() {
int sum = add(1, 2);
return 0;
}
工作原理:
main.c包含math_utils.h,编译器知道了add函数的返回类型、参数列表,以便在main.c中调用它,但并不知道函数体的具体实现。math_utils.c文件被单独编译,编译器将add函数编译成机器码。- 链接阶段,链接器将
main.c中对add函数的调用与math_utils.c中add函数的实际代码连接起来。
结构体、枚举、联合体重定义
这通常发生在头文件没有包含保护宏 的情况下。
错误示例代码:
config.h
struct Config {
int port;
char host[128];
};
main.c
#include "config.h"
#include "config.h" // 重复包含
int main() {
struct Config cfg;
cfg.port = 8080;
return 0;
}
编译错误信息:
In file included from main.c:1:
config.h:2:8: error: redefinition of 'struct Config'
struct Config {
^
main.c:1:1: note: previous definition of 'struct Config' was here
#include "config.h"
^
为什么错了?
每次 #include "config.h",编译器都会从头到尾解析一遍文件,第二次解析时,它发现 struct Config 这个类型已经被定义过了,所以报错。
如何修正:使用头文件保护宏
这是防止头文件内容被重复包含的标准做法。
config.h (修正后)
#ifndef CONFIG_H // CONFIG_H 这个宏没有被定义
#define CONFIG_H // 那么就定义它
struct Config {
int port;
char host[128];
};
#endif // 结束条件块
工作原理:
- 第一次
#include "config.h":CONFIG_H宏未定义,#ifndef条件为真。#define CONFIG_H执行,定义了宏。struct Config { ... };被正常处理。
- 第二次
#include "config.h":CONFIG_H宏已经被定义了,#ifndef条件为假。- 整个
#ifndef到#endif之间的代码块被完全跳过,不会再次解析struct Config。
- 整个
总结与最佳实践
| 错误类型 | 常见原因 | 解决方案 |
|---|---|---|
| 变量重定义 | 全局/静态变量在头文件中定义,且头文件被多次包含。 | 声明与定义分离:头文件中用 extern 声明,在一个且唯一一个 .c 文件中定义。 |
| 函数重定义 | 函数体在头文件中定义,且头文件被多次包含。 | 声明与定义分离:头文件中只放函数声明(原型),在对应的 .c 文件中实现函数体。 |
| 类型重定义 | 结构体、枚举等在头文件中定义,且头文件没有包含保护宏。 | 使用头文件保护宏 (#ifndef, #define, #endif)。 |
| 静态变量/函数 | static 修饰的全局变量或函数在多个 .c 文件中有定义。 |
static 的作用域限定在当前编译单元(.c 文件),每个需要它的 .c 文件都应该拥有自己的 static 定义,这不是错误,是设计,但如果误用了 extern,就会出问题。 |
核心原则:
- 头文件(
.h)的角色:提供接口,即声明,它应该告诉其他代码“有什么”和“能做什么”,但不应该包含具体的实现(即定义)。 - 源文件(
.c)的角色:提供实现,即定义,它包含了函数体、全局变量的实际存储等。 - 头文件保护宏:是所有头文件的“标配”,可以有效防止因重复包含而导致的编译错误。
遵循这些原则,你就可以几乎完全避免 "redefinition" 错误。
