函数的声明和定义

源文件生成可执行文件的过程:
image-20201113165158427

对象文件是根据源文件生成的,一个.cpp或者.c文件会生成一个.o文件; 链接是将所有的.o文件进行链接打包。

g++ -c xx.cpp : 生成对象文件xx.o
g++ xx.o:链接 xx.o 文件

1、函数未声明被调用

//test.cpp
#include <stdio.h>
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 函数没有声明。

2、函数未定义被调用

修改代码为如下:

//test1.cpp
#include <stdio.h>
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

函数未声明的错误产生于 “编译” 阶段,编译阶段检查的是语法错误
函数未定义的错误产生于 “链接” 阶段,链接阶段关心的是怎么实现

3、重复定义出现的错误

现有如下两个程序:

//test.cpp
#include <stdio.h>
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 <stdio.h>

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 <stdio.h>

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.ounite.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

重复定义。

4、补充

4.1 函数的声明与定义

#include <stdio.h>

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 <stdio.h>

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 <stdio.h>

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 ;
}

如果将 funcAfuncB 函数的定义注释掉:

#include <stdio.h>

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 <stdio.h>

//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 <stdio.h>

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 <stdio.h>

void funcA(int);
void funcB(int); //函数调用时关心的只是函数返回值,传入的参数类型而已,所以变量名也可以省略

int main() {
    funcA(5);
    return 0;
}
//union.cpp
#include <stdio.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 ;
}

union.cpp 文件中没有函数声明,编译无法通过,于是在该文件中加上函数声明:

//union.cpp
#include <stdio.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 ;
}

使用 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 <stdio.h>

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 <stdio.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 ;
}

分别编译两个文件生成对应的对象文件:

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)

出现错误“重复定义”。

4.2 头文件与源文件

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 <stdio.h> //从系统目录下进行查找
#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 <stdio.h> //从系统目录下进行查找
#include "header1.h" //从当前目录进行查找
#include "header2.h"

int main() {
    funcA(5);
    funcC(5, 6);
    return 0;
}

使用如下命令进行编译:

g++ -c test.cpp

(1) 如果交换 header1.hheader2.h 的引入顺序:

//test.cpp
#include <stdio.h> //从系统目录下进行查找
#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 <stdio.h> //从系统目录下进行查找
#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 展开预处理命令查看代码,会发现 funcAfuncB 函数被多次声明和定义。但是多次声明是可以的,多次定义就不行了,因为当调用的时候就不知道该调用哪个函数。
对于这种情况如何处理呢?
答:使用条件式宏

//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 <stdio.h>
#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 <stdio.h>
#include "header1.h" //调用了函数funcA

void funcC(int a, int b) {
    printf("funcC: a = %d, b = %d", a, b);
    funcA(a);
    return ;
}
//test.cpp
#include <stdio.h> //从系统目录下进行查找
#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

4.2.1 拓展:自定义头文件使用 <> 方式引入

函数的声明放在头文件中,相关的定义放在源文件中。
<>"" 的区别:包含进来的头文件查找方式不同,<> 是从系统路径下开始查找, "" 是从当前目录开始查找。
自定义的头文件也可以使用 <> 引入,只需要将头文件的路径添加到系统的搜索路径即可。使用如下命令:

#将当前目录添加到系统查找的目录列表中
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
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值