基础知识:篇2-多源文件编译过程

说明
  本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
  QQ 群 号:513683159 【相互学习】
内容来源
  CSDN-官方认证平平无奇说废话小天才-C语言编写头文件
  CSDN-mirror207-C语言头文件编写的几个基本规则
  CSDN-懒羊羊是程序猿-如何理解头文件?

上一篇:基础知识:篇1-单源文件编译过程
下一篇:基础知识:篇3-静态库与动态库

一、头文件的相关描述:

  在实现C语言模块化编程时,通常会用到*.h式的头文件,那头文件的作用是什么?头文件有什么固定格式?

1、头文件的作用

  1.定义可用的函数列表,方便查阅可调用的函数
  2定义宏定义(一些全局静态变量的定义),方便只修改头文件内容,程序便可做相应修改,无需修改源文件。
  3.头文件只是声明,不占内存空间,要知道其执行过程(具体函数定义),需进入对应的源文件里才可看到。
  4.源文件若包含某头文件,就等于赋予调用某些函数的权限。

2、头文件的格式

  头文件一般包含:函数声明,变量声明,常数定义,宏的定义等。
【头文件通用格式】

//第一模块:注释,版权,作者,重大修订记录等信息
/*   
	注释,版权,作者,重大修订记录等信息    
*/
//第二模块: 防重入开关,也就是常见的 #ifndef… #define… #endif
#ifndef  HEAD_FILE_NAME
#define  HEAD_FILE_NAME
/① ifndef  = if not define 
/② 表示:若没有定义HEAD_FILE_NAME(标识符)则定义标识符HEAD_FILE_NAME并包含头文件内容。
/	若定义HEAD_FILE_NAME(标识符)则不包含头文件内容
/	作用:故当多个源文件重复包含同一个头文件时,不会多次包含相同内容(不会造成大量声明冲突)。
/③ 标识符命名:自由命名但需唯一,常用规则:头文件名全大写,前后和.均为_.
/	如:delay.h  可写为 _DELAY_H_
//第三模块:C++ 编译器自适应开关,也就是常见的 #ifdef __cplusplus… extern “C” { } #endif
/若编译器是C++编译器,而调用的函数是C编译器生成的库,则需要该机制,否则编译后连接会提示找不到该函数
/extern "C" {...}
/表示:为C++编译器下正确链接到C库函数
/② __cplusplus 为所有C++编译器同意的宏定义。
/故#ifdef _cplusplus 表示:若C++编译器则执行下面内容


//第四模块: include 所有该文件中所使用的其它接口头文件,两层含义:
//一是说头文件应做到自包含,即使用头文件的用户不需要再为该头文件 include 其它头文件;
//二是从模块耦合内聚角度来说,头文件中本身不应该 include 太多其它头文件,一般就是通用数据类型定义,include 其它头文件意味着强耦合——引用了其它头文件中的类型定义,宏或是函数
/包含头文件,可有可无(不推荐头文件包含头文件)
#include<stdio.h>

//第五模块: 接口声明及注释,包括函数,结构体等,
//	 注意:不应出现全局变量和static 类型的接口,这些应放置在源文件中。
//函数注释:应包括功能说明,参数使用方法,可能的返回值,及其它注意事项。
//结构体注释:应包括每个成员变量所表示的含义

/函数声明,可有可无
void abcfun(int a,int b);

/结构体声明,可有可无
typedef struct{
  int a;
}ABC;

/宏定义,可有可无
#define MAX 100
#define MIN 0

#endif

3、头文件的调用方式 ( include <> 与include “” 的区别?)

  #include < > 引用的是编译器的类库路径里面的头文件。
  #include " " 引用的是程序目录的相对路径中的头文件,若在程序目录没有找到引用的头文件则到编译器的类库路径的目录下找该头文件。

二、情景说明:

  ubuntu16.04-server下利用gcc编译器进行多源文件编译后并执行。

第一步:创建文件

  1️⃣mymath.c文件

#include<stdio.h>
#include "add.h"
#include "sub.h"

int main(int argc, char **argv)
{
        int a = 10 ,b = 5;
        printf("%d + %d = %d\n",a,b,add(a,b));
        printf("%d - %d = %d\n",a,b,sub(a,b));
        return 0;
}

  2️⃣add.c文件

#include "add.h"

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

  3️⃣add.h文件

#ifndef _ADD_H_
#define _ADD_H_

int add(int a, int b);

#endif     

  4️⃣sub.c文件

#include "sub.h"

int sub(int a, int b)
{
    return a - b;
}

  5️⃣sub.h文件

#ifndef _SUB_H_
#define _SUB_H_

int sub(int a, int b);

#endif 

以上五个文件与以下文件等效

#include<stdio.h>
int add(int a,int b);
int sub(int a,int b);

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

int sub(int a, int b)
{
   return a - b;
}
int main(int argc, char **argv)
{
        int a = 10 ,b = 5;
        printf("%d + %d = %d\n",a,b,add(a,b));
        printf("%d - %d = %d\n",a,b,sub(a,b));
        return 0;
}

第二步:编译文件

  情景一:该五个文件均在同一文件夹/home/xsndz/mymath
  输入指令gcc mymath.c add.c sub.c -o mymath

  情景二:mymath.c、add.c、sub.c位于/home/xsndz/mymath
      add.h、sub.h位于/home/xsndz/mymath/include

  输入指令gcc mymath.c add.c sub.c -I ./include/ -o mymath
  PS:
    ①-I 绝对路径/相对路径 :指定头文件的位置
    (若不在同一文件夹下且没有指定头文件位置则会报错)
    ②生成可执行文件后,删除头文件/源文件,对头文件无影响。

第三步:执行文件

  输入指令./mymath
  则可在屏幕上打印出:

10 + 5 = 15
10 - 5 = 5

三、编译过程详解(第二步的详细说明):

1️⃣预编译过程
  输入指令gcc -E mymath.c add.c sub.c -I ./include/
不会输出对应的预编译文件(并不会产生.i文件),但会直接输出到屏幕。
  PS:
    gcc选项-o -c、-S、-E 搭配时不能有多个输入文件。
    原文提示:gcc: fatal error: cannot specify -o with -c, -S or -E with multiple files

2️⃣编译过程
  输入指令gcc -S mymath.c add.c sub.c -I ./include/
会输出对应的汇编文件:mymath.s、add.s、sub.s

3️⃣汇编过程
  输入指令gcc -c mymath.c add.c sub.c -I ./include/
会输出对应的目标文件:mymath.o、add.o、sub.o

4️⃣链接过程
  输入指令gcc mymath.c add.c sub.c -I ./include/
会输出对应的可执行文件:a.out

  PS:
  gcc选项
    -v选项:显示制作GCC工具自身时的配置命令;同时显示编译器驱动程序、预处理器、编译器的版本号。
    -o选项:任意阶段均可使用,指定文件名。
    -E选项:提示编译器执行完预处理就停下来,后边的编译、汇编、链接就先不执行了。
    -S选项:提示编译器执行完编译就停下来,不去执行汇编和链接了。
    -c选项:提示编译器执行完汇编就停下来。
  注意:-E、-S、-c选项相当于是限定了编译器执行操作的停止时间,而不是单独的将某一步拎出来执行

四、验证:

情景一:主函数中添加未在头文件声明的函数:(源文件定义,头文件未声明)

  1️⃣修改mymath.c文件

#include<stdio.h>
#include "add.h"
#include "sub.h"

int main(int argc, char **argv)
{
        int a = 10 ,b = 5;
        printf("%d + %d = %d\n",a,b,add(a,b));
        printf("%d - %d = %d\n",a,b,sub(a,b));
        add_printf(a,b);
        return 0;
}

  2️⃣修改add.c文件

#include<stdio.h>
#include "add.h"

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

int add_printf(int a, int b)
{
    return printf("%d + %d = %d\n",a,b,a+b);
}

  3️⃣编译文件
  输入指令gcc mymath.c add.c sub.c -I ./include/
  报错信息如下:
在这里插入图片描述
  原因:main函数未找到add_printf()函数。(头文件中未定义)
  解决方案:修改add.h文件

#ifndef _ADD_H_
#define _ADD_H_

int add(int a, int b);
int add_printf(int a,int b);
#endif     

情景二:主函数中添加未具体定义的函数(头文件声明,但源文件未定义)

  1️⃣修改mymath.c文件

#include<stdio.h>
#include "add.h"
#include "sub.h"

int main(int argc, char **argv)
{
        int a = 10 ,b = 5;
        printf("%d + %d = %d\n",a,b,add(a,b));
        printf("%d - %d = %d\n",a,b,sub(a,b));
        add_printf(a,b);
        return 0;
}

  2️⃣修改add.h文件

#ifndef _ADD_H_
#define _ADD_H_

int add(int a, int b);
int add_printf(int a,int b);
#endif   

  3️⃣编译文件
  输入指令gcc mymath.c add.c sub.c -I ./include/
  报错:
在这里插入图片描述
  原因:链接过程失败,没有链接到该函数的相关定义。
  解决方案:修改add.c文件

#include<stdio.h>
#include "add.h"

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

int add_printf(int a, int b)
{
    return printf("%d + %d = %d\n",a,b,a+b);
}

总结:

  以上两种情景证明,若以头文件,源文件方式进行链接,缺少任一都会造成编译出错,无法生成可执行文件。
  即:这边的链接过程会将程序所需函数都整合到可执行文件中,一旦生成可执行文件,你删除头文件或源文件均不会对可执行文件有影响。
  文件大小:8760在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值