要理解.c文件与.h文件有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件 4.连接阶段,将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件,当然,最后还可以用objcopy生成纯二进制码,也就是去掉了文件格式信息

编译器在编译时是以c文件为单位进行的,也就是说如果你的项目中一个c文件都没有,那么你的项目将无法编译,连接器是以目标文件为单位,它将一个或多个目 标文件进行函数与变量的重定位,生成最终的可执行文件,在PC上的程序开发,一般都有一个main函数,这是各个编译器的约定,当然,你如果自己写连接器 脚本的话,可以不用main函数作为程序入口!!!!

有了这些基础知识,再言归正传,为了生成一个最终的可执行文件,就需要一些目标文件,也就是需要C文件,而这些C文件中又需要一个main函数作为可执行程序的入口,那么我们就从一个C文件入手,假定这个C文件内容如下: #include <stdio.h> #include "mytest.h"

int main(int argc,char **argv) {  test = 25;  printf("test.................%d\n",test); }

mytest.h文件内容如下: int test;

现在以这个例子来讲解编译器的工作: 1.预处理阶段:编译器以C文件作为一个单元,首先读这个C文件,发现第一句与第二句是包含.h文件,就会在所有搜索路径中寻找这两个文件,找到之后,就 会将相应.h文件中再去处理宏,变量,函数声明,嵌套的.h文件包含等,检测依赖关系,进行宏替换,看是否有重复定义与声明的情况发生,最后将那些文件中 所有的东东全部扫描进这个当前的C文件中,形成一个中间“C文件”

2.编译阶段,在上一步中相当于将那个.h文件中的test变量扫描进了一个中间C文件,那么test变量就变成了这个文件中的一个全局变量,此时,就将 所有这个中间C文件的所有变量,函数分配空间,将各个函数编译成二进制码,按照特定目标文件格式生成目标文件,在这种格式的目标文件中进行各个全局变量, 函数的符号描述,将这些二进制码按照一定的标准组织成一个目标文件

3.连接阶段,将上一步成生的各个目标文件,根据一些参数,连接生成最终的可执行文件,主要的工作就是重定位各个目标文件的函数,变量等,相当于将个目标文件中的二进制码按一定的规范合到一个文件中

再回到c文件与h文件各写什么内容的话题上: 理论上来说C文件与h文件里的内容,只要是C语言所支持的,无论写什么都可以的,比如你在h文件中写函数体,只要在任何一个C文件包含此.h文件就可以将 这个函数编译成目标文件的一部分(编译是以C文件为单位的,如果不在任何C文件中包含此.h文件的话,这段代码就形同虚设),你可以在C文件中进行函数声 明,变量声明,结构体声明,这也不成问题!!!那为何一定要分成h文件与C文件呢?又为何一般都在h文件中进行函数,变量声明,宏声明,结构体声明呢?而 在C文件中去进行变量定义,函数实现呢??原因如下:

1.如果在h文件中实现一个函数体,那么如果在多个C文件中引用它,而且又同时编译多个C文件,将其生成的目标文件连接成一个可执行文件,在每个引用此h 文件的C文件所生成的目标文件中,都有一份这个函数的代码,如果这段函数又没有定义成局部函数,那么在连接时,就会发现多个相同的函数,就会报错

2.如果在h文件中定义全局变量,并且将此全局变量赋初值,那么在多个引用此h文件的C文件中同样存在相同变量名的拷贝,关键是此变量被赋了初值,所以编 译器就会将此变量放入DATA段,最终在连接阶段,会在DATA段中存在多个相同的变量,它无法将这些变量统一成一个变量,也就是仅为此变量分配一个空 间,而不是多份空间,假定这个变量在h文件没有赋初值,编译器就会将之放入BSS段,连接器会对BSS段的多个同名变量仅分配一个存储空间

3.如果在C文件中声明宏,结构体,函数等,那么我要在另一个C文件中引用相应的宏,结构体,就必须再做一次重复的工作,如果我改了一个C文件中的一个声 明,那么又忘了改其它C文件中的声明,这不就出了大问题了,程序的逻辑就变成了你不可想象的了,如果把这些公共的东东放在一个头文件中,想用它的C文件就 只需要引用一个就OK了!!!这样岂不方便,要改某个声明的时候,只需要动一下h文件就行了

4.在h文件中声明结构体,函数等,当你需要将你的代码封装成一个库,让别人来用你的代码,你又不想公布源码,那么人家如何利用你的库呢?也就是如何利用 你的库中的各个函数呢??一种方法是公布源码,别人想怎么用就怎么用,另一种是提供头文件,别人从头文件中看你的函数原型,这样人家才知道如何调用你写的 函数,就如同你调用printf函数一样,里面的参数是怎样的??你是怎么知道的??还不是看人家的头文件中的相关声明啊!!!当然这些东东都成了C标 准,就算不看人家的头文件,你一样可以知道怎么使用

例子: //a.h void foo();  //a.c #include "a.h"  //我的问题出来了:这句话是要,还是不要? void foo() {       return; }

//main.c #include "a.h" int main(int argc, char *argv[]) {       foo();     return 0; }

针对上面的代码,请回答三个问题: 1.a.c 中的#include "a.h" 这句话是不是多余的?为什么经常见xx.c 里面include 对应的xx.h? 2.如果a.c 中不写,那么编译器是不是会自动把.h 文件里面的东西跟同名的.c 文件绑定在一起? 3.第三个问题我给他改了一下:如果a.c 中不写include<>,那么编译器是不是会自动把.h 文件里面的东西跟同名的.c文件绑定在一起?

下面是一位牛人的原话:

从C编译器角度看,.h和.c皆是浮云,就是改名为.txt、.doc也没有大的分别。换句话说,就是.h和.c没啥必然联系。.h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。这个声明有啥用?只是让需要用这些声明的地方方便引用。因为#include "xx.h" 这个宏其实际意思就是把当前这一行删掉,把xx.h 中的内容原封不动的插入在当前行的位置。由于想写这些函数声明的地方非常多(每一个调用xx.c 中函数的地方,都要在使用前声明一下子),所以用#include "xx.h" 这个宏就简化了许多行代码——让预处理器自己替换好了。也就是说,xx.h 其实只是让需要写xx.c 中函数声明的地方调用(可以少写几行字),至于include 这个.h 文件是谁,是.h 还是.c,还是与这个.h 同名的.c,都没有任何必然关系。        这样你可能会说:啊?那我平时只想调用xx.c 中的某个函数,却include了xx.h 文件,岂不是宏替换后出现了很多无用的声明?没错,确实引入了很多垃圾 ,但是它却省了你不少笔墨,并且整个版面也看起来清爽的多。鱼与熊掌不可得兼,就是这个道理。反正多些声明(.h一般只用来放声明,而放不定义,参见拙著“过马路,左右看”)也无害处,又不会影响编译,何乐而不为呢? 翻回头再看上面的3个问题,很好解答了吧?

它的解答如下: 1.不一定。这个例子中显然是多余的。但是如果.c中的函数也需要调用同个.c中的其它函数,那么这个.c往往会include同名的.h,这样就不需要为声明和调用顺序而发愁了(C语言要求使用之前必须声明,而include同名.h一般会放在.c的开头)。有很多工程甚至把这种写法约定为代码规范,以规范出清晰的代码来。 2.答:1中已经回答过了。 3.答:不会。问这个问题的人绝对是概念不清,要不就是想混水摸鱼。非常讨厌的是中国的很多考试出的都是这种烂题,生怕别人有个清楚的概念了,绝对要把考生搞晕。

补充一下:

一个程序仅有一个H文件,没有C文件,可以吗?

答案是肯定的,不可以!在VC6.0下可以看到编译按钮的颜色!变灰,表示无法编译。

C语言中 .h文件和.c文件的区别的更多相关文章

  1. C语言中.h和.c文件解析(很精彩)

    C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析 ...

  2. C语言中.h和.c文件解析

    整理自C语言中.h和.c文件解析(很精彩) Part.1(林锐<高质量C/C++编程>) 通过头文件来调用库功能.在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的 ...

  3. 转-C语言中.h和.c文件解析

    C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程:       1.预处理阶段 2.词 ...

  4. C语言中.h和.c文件解析(转载)

    转载:http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html   简单的说其实要理解C文件与头文件(即.h)有什么不同之处, ...

  5. C/C++语言中NULL、'\0’和0的区别

    注:本文参考了http://blog.csdn.net/mylinx/article/details/6873253及书籍<征服C指针>([日]前桥和弥著). NULL.'\0'和0的值是 ...

  6. c语言中static 函数和普通函数的区别

    C程序一直由下列部分组成: 1)正文段——CPU执行的机器指令部分:一个程序只有一个副本:只读,防止程序由于意外事故而修改自身指令: 2)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放 ...

  7. Go语言中 Print,Println 和 Printf 的区别(八)

    Print 和 Println 这两个打印方式类似,只在格式上有区别 1. Println 打印的每一项之间都会有空行,Print 没有,例如: fmt.Println("go", ...

  8. 对于.h文件和.c文件

    C语言中.h文件和.c文件详细解析_云止水_新浪博客http://blog.sina.com.cn/s/blog_73006d600102wcx5.html

  9. C语言中的.h和.c文件

    1.h为头文件,.c为源文件,其实两者都是代码,没有实质性的区别,只是后缀不一样,是一种编程规范,主要是为了解决定义与调用之间的混乱. 2.h文件一般写一些函数声明.宏定义.结构体等内容:c文件是程序 ...

随机推荐

  1. Java面试通关秘籍汇总集

    一.基础篇 1.1.Java基础 面向对象的特征:继承.封装和多态 final, finally, finalize 的区别 Exception.Error.运行时异常与一般异常有何异同 请写出5种常 ...

  2. [LeetCode&Python] Problem 720. Longest Word in Dictionary

    Given a list of strings words representing an English Dictionary, find the longest word in words tha ...

  3. EasyUI学习总结(一)——EasyUI入门

    一.EasyUI下载 EasyUI官方下载地址:http://www.jeasyui.com/download/index.php,目前最新的版本是:jQuery EasyUI 1.4.1

  4. Netty学习记录

    一.Netty简介 Netty 是一个基于 JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性. Netty 是一个 NIO client-s ...

  5. C#程序终止问题CLR20R3解决方法

    去年在公司局域网部署了一个C#编写的自动更新的工具软件,最近有同事反映部分Win7系统电脑安装不了,程序自动安装不了,免安装版又运行不了. 没办法,先解决自动安装不了的问题,最后通过关闭防火墙得以解决 ...

  6. WIN7X64SP1极限精简版by双心

     WIN7X64SP1极限精简版by双心 http://bbs.wuyou.net/forum.php?mod=viewthread&tid=405044&page=1&ext ...

  7. 在Release版本下使用VLD

    前提 同Debug版本在VC中配置好VLD的相关信息,拷贝 Visual Leak Detector\bin\Win32目录下所有的文件和vld.ini到工程目标路径下. 强制检测 在程序入口处的cp ...

  8. 学习笔记之Problem Solving with Algorithms and Data Structures using Python

    Problem Solving with Algorithms and Data Structures using Python — Problem Solving with Algorithms a ...

  9. Android - 简单listview

    //MainActivity.java package com.example.zc.listviewdemo; import android.support.v7.app.AppCompatActi ...

  10. inode引起的Linux无法创建新文件,磁盘空间不足

    df -h,判断硬盘空间是否已经满了,占用率达100% ,就可以断定该分区满了. df -ia,占用率达100%,也会导致无法创建新文件.一般都是存在大量小文件引起的. inode包含文件的元信息,具 ...