一起学习C语言:函数(二)

   上一篇<一起学习C语言:函数(一)> 中,我们了解了函数的概念,以及函数实现与程序编译过程。本章节,我们分析内部函数和外部函数,以及变量的生命周期。

章节预览:


4. 外部函数与内部函数
4.1 外部函数
4.2 内部函数
5. 变量的生命周期与作用域
目录预览

章节内容:


4. 外部函数与内部函数

   默认情况下,我们定义或声明的函数属于“外部”函数,又称为“全局”函数。全局函数即可以被本文件中的其他函数调用,也可以被其他文件中的函数调用。首先,我们分析函数如何在其他文件中调用到。
     接下来,编写一个多文件工程(预编译加载函数实现):

       math.c代码:

           int Add(int a, int b)
           {
               return a + b;
           }

       main.c代码:

           #include <stdio.h>
           #include “math.c”

           int main()
           {
               int res = Add(2, 3);
               printf(“res:%d.”, res);
               return 0;
           }

       在这个工程中,math.c属于源文件,main.c属于主文件(包含main函数)。 我们在math.c文件内实现了Add全局函数,在main.c内的main内调用Add函数。这个程序中,我们相当于把math.c当做头文件使用,为什么是这样呢?接下来,我们分析一下编译过程:

       一. 预编译

扫描二维码关注公众号,回复: 11489702 查看本文章

           在控制台中输入gcc -E main.c -o main.i,预处理器把main.c中包含的文件经过预处理后写入main.i,打开main.i文件可以看到stdio.h和math.c文件预处理后的内容,而Add函数定义也在这一步保存在了main.i文件中。

       二. 编译为汇编代码

           在控制台中输入gcc -S main.i -o main.s,编译器把main.i中的内容转换为汇编代码后写入main.s,打开main.s文件可以看到Add和main函数定义的汇编实现代码,以及main函数调用Add函数的过程。

       三. 生成为目标文件

           在控制台中输入gcc -c main.s -o main.o,汇编器把mian.s中的内容编译为机器码后写入main.o,这里面就包含了供程序执行使用的Add和main函数的实现部分(可以通过hexdump -C main指令查看具体内容)。

       四. 链接

           在控制台中输入gcc -o main main.o,链接器把main.o中的Add和main函数实现部分打包生成一个可执行程序main(可以通过hexdump -C main指令查看具体内容)。

       实际编译

           实际编译时我们不需要了解其中的细节,控制台中输入gcc -o main main.c。


     另一种形式,编写一个多文件工程(链接加载函数实现):

       math.c代码:

           int Add(int a, int b)
           {
               return a + b;
           }

       math.h代码:

           extern int Add(int a, int b);

       main.c代码:

           #include <stdio.h>
           #include “math.h”

           int main()
           {
               int res = Add(2, 3);
               printf(“res:%d.”, res);
               return 0;
           }

       在这个工程中,math.c属于源文件,math.h属于头文件,main.c属于主文件(包含main函数)。我们在math.c文件内实现了Add全局函数,在math.h文件内声明Add全局函数,在main.c内的main内调用Add函数。这个程序中,我们相当于把math.c当做外部库使用(链接时加载),而math.h内的Add函数声明作为生成目标文件(main.o)时的Add函数原型配对。

       实际编译

           在上个示例中,我们了解工程编译过程,这里我们简单介绍一下编译流程。
           控制台中输入gcc -o main main.c math.c,这里相当于把main.c和math.c当做两个模块分别执行预编译、编译为汇编代码、生成为目标文件,每一步得到两个文件,比如分别执行gcc -E main.c -o main.i、gcc -E math.c -o math.i后,得到main.i和math.i。执行到链接时,把main.o和math.o中的Add、main函数实现部分打包生成一个可执行程序main。


4.1 外部函数

   上述示例中,我们了解了自定义函数如何编译到可执行程序中,以及在别的文件内的调用方式。实际编程时,如果被调用的函数定义不在本文件中,可以通过在函数声明左侧增加extern关键字,表示这个函数来自外部(别的文件或外部库)。比如,我们为程序执行预编译操作后,查看main.i文件可以了解到Add函数声明,参考图4-1。

在这里插入图片描述

图4-1 预处理文件

   C语言中,函数声明分为两种形式:一种是形式声明(函数原型,上述所说的函数声明),另一种是实体声明(函数定义)。
   当程序编译时,一个函数可以存在多个形式声明,只能存在一个实体声明。比如上述示例中,可以在main.c中形式声明Add函数,但不能实体声明Add函数。
   形式声明的作用也可以分为两种情况:一种是为了编程人员方便查看函数原型,另一种是在生成目标文件时编译器匹配函数原型。


4.2 内部函数

   在定义函数时,可以通过指定函数存储类别表示函数的作用域 (6)。函数存储类别分为“static”和上述所说的“extern”,其中static表示内部函数,extern表示外部函数。
  定义函数时,如果省略extern关键字,则默认为是外部函数。但定义内部函数时,不能省略static关键字。
       内部函数定义形式

           static 返回类型 函数名称(参数列表)
           {
               函数体
           }

       内部函数定义举例

           static void func(int a)
           {
               printf(“a的值为:%d”, a);
           }

       内部函数又称为静态函数,它只能被本文件内的其他函数所调用。使用内部函数可以确保其他文件中即使有相同命名的内部函数,也互不干扰。


   (6):编程世界中,作用域一般表示某个变量、某个常量、某个函数的可使用范围区域。比如Add全局函数可以在math.c文件内使用,也可以在main.c文件内使用,它的作用域属于整个工程内。


5. 变量的生命周期与作用域

   在之前编写的示例中,我们都是在函数中定义变量。比如在main函数中定义int a、自实现函数中定义char c等,这些变量随函数入栈 (7) 后分配栈内的内存空间(一般称作压入栈),然后随函数出栈时反顺序出栈(后入先出),最后栈地址跳转到函数入栈时的地址。从这里我们可以了解到,函数中定义的变量生命周期随函数入栈分配变量内存空间生效,随函数出栈时失效,这类变量统称为局部变量。当然,局部变量还有另外一种情况,在变量定义时指定存储类别改变变量的生命周期。
   总体来看,在一个函数内部定义的变量只在本函数内有效,也可以认为只能在本函数中使用这个变量,而这个变量的有效范围也是由“作用域”表示。

   (7):栈内存属于扩充形式增长内存。比如当前栈内存分配100k,如果在一个函数中栈内存用到500k,则栈内存自动扩充到500k(大致数据,满足栈内存需求),当这个函数退出后栈内存还是会保持在500k,一直到进程退出后归还栈内空间。


       首先,我们了解局部变量的定义方式:

           1.在函数内的开头定义变量

在这里插入图片描述

           2.在函数内的复合语句内定义变量:

在这里插入图片描述

           3.在函数内的执行语句后定义变量:

在这里插入图片描述

           4.函数内的形参变量:

在这里插入图片描述

   如果我们需要变量在多个函数内部都允许使用时,可以定义全局变量。全局变量定义在函数外部,又称为全程变量,可以在本文件或别的文件内的不同函数内部使用。
   通常情况下,全局变量的有效区域在定义变量处至文件末尾,生命周期从程序开始执行到程序执行结束。

       接下来,我们了解全局变量的定义方式:

在这里插入图片描述

   全局变量定义在预处理、宏定义下方至函数之间比较常见,如int a,可以在本文件中的所有函数内部使用。示例中,全局变量a和c随main函数执行生效至main函数退出失效。


目录预览


<一起学习C语言:C语言发展历程以及定制学习计划>
<一起学习C语言:初步进入编程世界(一)>
<一起学习C语言:初步进入编程世界(二)>
<一起学习C语言:初步进入编程世界(三)>
<一起学习C语言:C语言数据类型(一)>
<一起学习C语言:C语言数据类型(二)>
<一起学习C语言:C语言数据类型(三)>
<一起学习C语言:C语言基本语法(一)>
<一起学习C语言:C语言基本语法(二)>
<一起学习C语言:C语言基本语法(三)>
<一起学习C语言:C语言基本语法(四)>
<一起学习C语言:C语言基本语法(五)>
<一起学习C语言:C语言循环结构(一)>
<一起学习C语言:C语言循环结构(二)>
<一起学习C语言:C语言循环结构(三)>
<一起学习C语言:数组(一)>
<一起学习C语言:数组(二)>
<一起学习C语言:数组(三)>
<一起学习C语言:初谈指针(一)>
<一起学习C语言:初谈指针(二)>
<一起学习C语言:初谈指针(三)>
<一起学习C语言:函数(一)>

猜你喜欢

转载自blog.csdn.net/a29562268/article/details/107736352