对象文件是根据源文件生成的,一个.cpp
或者.c
文件会生成一个.o
文件; 链接是将所有的.o
文件进行链接打包。
g++ -c xx.cpp
: 生成对象文件xx.o
g++ xx.o
:链接 xx.o
文件
//test.cpp
#include
int funcA(int n) {
if (n == 0) return 0;
printf("funcA:%d\n", n);
funcB(n--);
return 0;
}
int main() {
funcA(5);
return 0;
}
g++ test.cpp
编译出现如下错误:
test.cpp: In function ‘int funcA(int)’:
test.cpp:21:5: error: ‘funcB’ was not declared in this scope
funcB(n--);
^~~~~
test.cpp:21:5: note: suggested alternative: ‘funcA’
funcB(n--);
^~~~~
funcA
提示 funcB
函数没有声明。
修改代码为如下:
//test1.cpp
#include
int funcB(int);//声明
int funcA(int n) {
if (n == 0) return 0;
printf("funcA:%d\n", n);
funcB(n--);
return 0;
}
int main() {
funcA(5);
return 0;
}
编译出现如下错误:
/tmp/ccmEKKMN.o: In function `funcA(int)':
test1.cpp:(.text+0x3a): undefined reference to `funcB(int)'
collect2: error: ld returned 1 exit status
也即函数没有定义的错误。
根据源文件生成可执行文件的过程,如果 编译 test1.cpp` 生成对象文件:
% g++ -c test1.cpp
% ll
total 80K
-rw-rw-r-- 1 root root 563 Sep 23 21:18 test1.cpp
-rw-rw-r-- 1 root root 1.8K Sep 23 21:23 test1.o
可见成功生成了对象文件 test1.o
。
但是链接时出现错误:
% g++ test1.o
test1.o: In function `funcA(int)':
test1.cpp:(.text+0x3a): undefined reference to `funcB(int)'
collect2: error: ld returned 1 exit status
函数未声明的错误产生于 “编译” 阶段,编译阶段检查的是语法错误
函数未定义的错误产生于 “链接” 阶段,链接阶段关心的是怎么实现
现有如下两个程序:
//test.cpp
#include
int funcB(int);//声明
int funcA(int n) {
if (n == 0) return 0;
printf("funcA:%d\n", n);
funcB(n--);
return 0;
}
int main() {
funcA(5);
return 0;
}
//unite.cpp
#include
int funcB(int n) {
if (n == 0) return 0;
printf("funcB:%d\n", n);
return 0;
}
编译生成对象文件并链接:
g++ -c test.cpp
g++ -c unite.cpp
g++ test.o unite.o
成功生成 a.out
文件,执行a.out
可以顺利得到想要的结果。
但是如果 test.cpp
如下:
#include
int funcB(int n) {
if (n == 0) return 0;
printf("funcB:%d\n", n);
return 0;
}
int funcB(int);//声明
int funcA(int n) {
if (n == 0) return 0;
printf("funcA:%d\n", n);
funcB(n--);
return 0;
}
int main() {
funcA(5);
return 0;
}
再链接 test.o
和 unite.o
,就会出现如下错误:
% g++ test.o unite.o
unite.o: In function `funcB(int)':
unite.cpp:(.text+0x0): multiple definition of `funcB(int)'
test.o:test.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
重复定义。
#include
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1); //该函数未声明
return ;
}
int add(int a, int b) { //函数的声明和定义都放在了定义里面
return a + b;
}
int main() {
return 0;
}
编译出现如下错误:
define % g++ test.cpp
test.cpp:6:5: error: use of undeclared identifier 'funcB'; did you mean 'funcA'? #funcB函数未声明
funcB(n - 1);
^~~~~
funcA
test.cpp:3:6: note: 'funcA' declared here
void funcA(int n) {
^
1 error generated.
解决思路是声明并定义funcB
方法。但是如果在此时又在 funcB
中调用了 funcA
,如下代码所示:
#include
void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
int add(int a, int b) { //函数的声明和定义在一起,
return a + b;
}
int main() {
return 0;
}
这就成了一个循环调用,无论哪个函数定义在前面都没法解决。编译报的错误依然是:
maureen@Maureen define % g++ test.cpp
test.cpp:13:5: error: use of undeclared identifier 'funcA'; did you mean 'funcB'? #funcA函数未声明
funcA(n - 1);
^~~~~
funcB
test.cpp:10:6: note: 'funcB' declared here
void funcB(int n) {
^
1 error generated.
所以需要主动地进行函数声明:将函数声明和定义分开
#include
void funcA(int);
void funcB(int); //函数调用时关心的只是函数返回值,传入的参数类型而已,所以变量名也可以省略
int main() {
funcA(5);
return 0;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
如果将 funcA
和 funcB
函数的定义注释掉:
#include
void funcA(int);
void funcB(int); //函数调用时关心的只是函数返回值,传入的参数类型而已,所以变量名也可以省略
int main() {
funcA(5);
return 0;
}
/*void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
*/
编译结果为:
maureen@Maureen define % g++ test.cpp
Undefined symbols for architecture x86_64: #Undefined-未定义
"funcA(int)", referenced from:
_main in test-5f1cde.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation) #链接过程中出现错误
问:“函数未声明(Undefined)” 错误 和 “函数未定义(Undecared)”错误各自暴露在什么阶段呢?
答:从源文件.c
到可执行程序 a.out
经历了如下几个阶段:
.o
,生成对象文件的命令为 g++ -c xx.cpp
g++ xx.o
验证以上说法:
1、函数未声明 + 未定义
#include
//void funcA(int);
//void funcB(int); //函数调用时关心的只是函数返回值,传入的参数类型而已,所以变量名也可以省略
int main() {
funcA(5);
return 0;
}
/*void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
*/
使用 g++ -c *.cpp
将源文件编译成对象文件,这是编译阶段。“函数未声明”错误就暴露在这个阶段:
maureen@Maureen define % g++ -c test.cpp #(编译)生成对应的对象文件
test.cpp:14:5: error: use of undeclared identifier 'funcA'
funcA(5);
^
1 error generated.
2、函数声明却未定义
#include
void funcA(int);
void funcB(int); //函数调用时关心的只是函数返回值,传入的参数类型而已,所以变量名也可以省略
int main() {
funcA(5);
return 0;
}
/*void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
*/
g++ -c test.cpp
可成功执行,生成对应的 test.o
对象文件,但是使用 g++ test.o
进行链接时出现了错误:
maureen@Maureen define % g++ test.o #链接阶段
Undefined symbols for architecture x86_64:
"funcA(int)", referenced from:
_main in test.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
总结:"函数未声明"错误发生在编译阶段,"函数未定义"错误发生在链接阶段。
3、将函数的声明和定义放在不同的文件中
//test.cpp
#include
void funcA(int);
void funcB(int); //函数调用时关心的只是函数返回值,传入的参数类型而已,所以变量名也可以省略
int main() {
funcA(5);
return 0;
}
//union.cpp
#include
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
union.cpp
文件中没有函数声明,编译无法通过,于是在该文件中加上函数声明:
//union.cpp
#include
void funcA(int);
void funcB(int);
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
使用 g++ -c
命令将两个 .cpp
文件都生成对应的对象文件:
g++ -c test.cpp
g++ -c union.cpp
将两个文件进行链接并执行程序:
g++ test.p unite.o #链接对象文件
./a.out #执行程序
funcA:5
funcB:4
funcA:3
funcB:2
funcA:1
4、同一路径下两个文件都包含函数的声明和定义
//test.cpp
#include
void funcA(int);
void funcB(int); //函数调用时关心的只是函数返回值,传入的参数类型而已,所以变量名也可以省略
int main() {
funcA(5);
return 0;
}
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
//union.cpp
#include
void funcA(int);
void funcB(int);
void funcA(int n) {
if (n == 0) return ;
printf("funcA:%d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB:%d\n", n);
funcA(n - 1);
return ;
}
分别编译两个文件生成对应的对象文件:
g++ -c test.cpp
g++ -c unite.cpp
将对象文件进行链接:
maureen@Maureen define % g++ test.o union.o #链接对象文件
duplicate symbol 'funcB(int)' in:
test.o
union.o
duplicate symbol 'funcA(int)' in:
test.o
union.o
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
出现错误“重复定义”。
1、将函数的声明和定义都放在头文件中
//header1.h
void funcA(int);
void funcB(int);
void funcA(int n) {
if (n == 0) return ;
printf("funcA: %d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB: %d\n", n);
funcA(n - 1);
return ;
}
//test.cpp
#include //从系统目录下进行查找
#include "header1.h" //从当前目录进行查找
int main() {
funcA(5);
return 0;
}
test.cpp
可成功编译,且编译成功后的可执行文件也能成功执行。
之所以能成功,是因为 #include
预处理命令会将 include
后面的文件原封不动地拷贝到当前位置。
2、如果此时 test
文件要调用一个函数 funcC
,在header2.h
中声明和定义 funcC
//header1.h
void funcA(int);
void funcB(int);
void funcA(int n) {
if (n == 0) return ;
printf("funcA: %d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB: %d\n", n);
funcA(n - 1);
return ;
}
//header2.h
void funcC(int, int);
void funcC(int a, int b) {
printf("funcC: a = %d, b = %d", a, b);
funcA(a);
return ;
}
//test.cpp
#include //从系统目录下进行查找
#include "header1.h" //从当前目录进行查找
#include "header2.h"
int main() {
funcA(5);
funcC(5, 6);
return 0;
}
使用如下命令进行编译:
g++ -c test.cpp
(1) 如果交换 header1.h
和 header2.h
的引入顺序:
//test.cpp
#include //从系统目录下进行查找
#include "header2.h" //从当前目录进行查找
#include "header1.h"
int main() {
funcA(5);
funcC(5, 6);
return 0;
}
g++ -c test.cpp
编译会出现错误:
In file included from test.cpp:9:
./header2.h:12:5: error: use of undeclared identifier 'funcA'
funcA(a);
^
1 error generated.
(2) 系统头文件如果使用 include
包含多次也是OK的,但是自己写的头文件,如果引用多次就会出现问题:
//test.cpp
#include //从系统目录下进行查找
#include "header1.h" //从当前目录进行查找
#include "header1.h"
#include "header2.h"
int main() {
funcA(5);
funcC(5, 6);
return 0;
}
编译出现错误:
In file included from test.cpp:10:
./header1.h:11:6: error: redefinition of 'funcA'
void funcA(int n) {
^
./header1.h:11:6: note: previous definition is here
void funcA(int n) {
^
In file included from test.cpp:10:
./header1.h:18:6: error: redefinition of 'funcB'
void funcB(int n) {
^
./header1.h:18:6: note: previous definition is here
void funcB(int n) {
^
2 errors generated.
#include
是预处理命令,可以使用 g++ -E
展开预处理命令查看代码,会发现 funcA
和 funcB
函数被多次声明和定义。但是多次声明是可以的,多次定义就不行了,因为当调用的时候就不知道该调用哪个函数。
对于这种情况如何处理呢?
答:使用条件式宏。
//header1.h
#ifndef _HEADER1_H
#define _HEADER1_H
void funcA(int);
void funcB(int);
void funcA(int n) {
if (n == 0) return ;
printf("funcA: %d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB: %d\n", n);
funcA(n - 1);
return ;
}
#endif
//header2.h
#ifndef _HEADER2_H
#define _HEADER2_H
void funcC(int, int);
void funcC(int a, int b) {
printf("funcC: a = %d, b = %d", a, b);
funcA(a);
return ;
}
#endif
然后再编译 test.cpp
(包含了两次header1.h
)就可以成功通过。
三行宏定义可以解决一次源文件编译时的重复包含问题。
工程规范:和头文件(*.h)
对应的源文件是*.cc
,且名称相同。
ld
命令可以查看 .o
文件中包含的定义:
maureen@Maureen define % ld test.o
Undefined symbols for architecture x86_64:
"_printf", referenced from:
__Z5funcAi in test.o
__Z5funcBi in test.o
__Z5funcCii in test.o
ld: symbol(s) not found for architecture x86_64
如果在头文件中包含了其他头文件,为了避免重复包含问题,将函数的声明和定义分开写,声明放在头文件中,定义放在源文件中。
规范的工程项目开发 演示:
//header1.h //头文件
#ifndef _HEADER1_H
#define _HEADER1_H
void funcA(int);
void funcB(int);
#endif
//header1.cc //头文件header1.h对应的写函数定义的源文件
#include
#include "header1.h" //包含函数声明
void funcA(int n) {
if (n == 0) return ;
printf("funcA: %d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB: %d\n", n);
funcA(n - 1);
return ;
}
//header2.h
#ifndef _HEADER2_H
#define _HEADER2_H
void funcC(int, int);
#endif
//header2.cc
#include
#include "header1.h" //调用了函数funcA
void funcC(int a, int b) {
printf("funcC: a = %d, b = %d", a, b);
funcA(a);
return ;
}
//test.cpp
#include //从系统目录下进行查找
#include "header1.h" //从当前目录进行查找
#include "header2.h"
int main() {
funcA(5);
funcC(5, 6);
return 0;
}
然后,生成各个源文件对应的对象文件:
g++ -c header1.cc
g++ -c header2.cc
g++ -c test.cpp
接着,链接对象文件
g++ header1.o header2.o test.o
最后,运行程序:
./a.out
<>
方式引入函数的声明放在头文件中,相关的定义放在源文件中。
<>
和 ""
的区别:包含进来的头文件查找方式不同,<>
是从系统路径下开始查找, ""
是从当前目录开始查找。
自定义的头文件也可以使用 <>
引入,只需要将头文件的路径添加到系统的搜索路径即可。使用如下命令:
#将当前目录添加到系统查找的目录列表中
g++ -I./
g++ -I./ -c header1.cc
g++ -I./ -c header2.cc
g++ -I./ -c test.cpp
g++ test.o header1.o header2.o
./a.out