Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3842704
  • 博文数量: 146
  • 博客积分: 3918
  • 博客等级: 少校
  • 技术积分: 8584
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-17 13:52
个人简介

个人微薄: weibo.com/manuscola

文章分类

全部博文(146)

文章存档

2016年(3)

2015年(2)

2014年(5)

2013年(42)

2012年(31)

2011年(58)

2010年(5)

分类: LINUX

2012-07-28 10:21:02

    N年前学习C语言开始,就被老师教导,要记得包含头文件。自己也养成了二话不说就#include的习惯。从来没有静下信来想想,C语言必需要有头文件吗?头文件到底起到什么的作用。

    最近一段时间做了一些静态库和动态库相关的东西,一些内容在上一篇博文中,阅读上一篇博文的看官可以看出,我的静态库和动态库都没有对应的头文件,可是我的应用程序调用了库,没有包含头文件,一样是正常地编译执行。意识到这个问题的时候,我还有一阵恐慌,不知道怎么解释这个现象。因为我们常规都会包含头文件,比如调用多线程库,都会 #include

    对于这个问题,我又做了一些实验算是把这个问题有个初步的理解。当然这个题目起的有点标题党,这么大的题目不是我这种菜鸟能够驾驭的了的,但是我还是想把自己的理解说以下,欢迎各位路过的高手批评指点。

    我写了一个测试程序,里面没有包含任何的头文件。

  1. int f()
  2. {
  3.         while(1)
  4.         {
  5.                 printf("hello\n");
  6.                 sleep(5);
  7.         }
  8. }


  9. int main()
  10. {
  11.         creat("testfile",0777);
  12.         // printf("hello world\n");
  13.         unsigned long tid = 0;
  14.         int ret = pthread_create(&tid,0,&f,0);
  15.         if(ret)
  16.         {
  17.                 printf("pthread_create failed \n");
  18.         }
  19.         else
  20.         {
  21.                 printf("tid = %lu\n",tid);
  22.                 pthread_join(tid,0);
  23.         }


  24.         return 0;
  25. }

    这个测试程序中调用了printf,sleep,creat,pthread_create,he pthread_join,按照惯例,我们是需要头文件的,需要的头文件如下:
  1. //for printf
  2. #include<stdio>

  3. // for sleep
  4. #include<unistd.h>

  5. //for create
  6. #include <sys/types.h>
  7. #include <sys/stat.h>
  8. #include <fcntl.h>

  9. //for pthread_create ,pthread_join
  10. #include <pthread.h>
    现在我们观察,能否编译通过:

  1. root@libin:~/program/C/testlib/head# gcc -o test test.c
  2. test.c: In function ‘f’:
  3. test.c:6: warning: incompatible implicit declaration of built-in function ‘printf’
  4. test.c: In function ‘main’:
  5. test.c:20: warning: incompatible implicit declaration of built-in function ‘printf’
  6. test.c:24: warning: incompatible implicit declaration of built-in function ‘printf’
  7. /tmp/cc2Rt0UO.o: In function `main':
  8. test.c:(.text+0x65): undefined reference to `pthread_create'
  9. test.c:(.text+0xa6): undefined reference to `pthread_join'
  10. collect2: ld returned 1 exit status
    这是预料之中的事情,因为,没有找到pthread_create 和pthread_join两个函数的定义。这是因为链接时,没有指定libpthread.so。加上-lpthread自然能够编过。 有的看官就问了,为什么creat sleep 和printf为什么不报找不到函数定义。那是因为这些函数都在libc库中,而libc库不需要显式指定,默认就包换了libc的动态库。


  1. root@libin:~/program/C/testlib/head# ll
  2. 总用量 12
  3. drwxr-xr-x 2 root root 4096 2012-07-28 10:41 ./
  4. drwxr-xr-x 5 root root 4096 2012-07-27 19:05 ../
  5. -rw-r--r-- 1 root root 405 2012-07-28 10:40 test.c
  6. root@libin:~/program/C/testlib/head# gcc -Wall -o test test.c -lpthread
  7. test.c: In function ‘f’:
  8. test.c:6: warning: implicit declaration of function ‘printf’
  9. test.c:6: warning: incompatible implicit declaration of built-in function ‘printf’
  10. test.c:7: warning: implicit declaration of function ‘sleep’
  11. test.c: In function ‘main’:
  12. test.c:14: warning: implicit declaration of function ‘creat’
  13. test.c:17: warning: implicit declaration of function ‘pthread_create’
  14. test.c:20: warning: incompatible implicit declaration of built-in function ‘printf’
  15. test.c:24: warning: incompatible implicit declaration of built-in function ‘printf’
  16. test.c:25: warning: implicit declaration of function ‘pthread_join’
  17. root@libin:~/program/C/testlib/head# ll
  18. 总用量 20
  19. drwxr-xr-x 2 root root 4096 2012-07-28 10:41 ./
  20. drwxr-xr-x 5 root root 4096 2012-07-27 19:05 ../
  21. -rwxr-xr-x 1 root root 7394 2012-07-28 10:41 test*
  22. -rw-r--r-- 1 root root 405 2012-07-28 10:40 test.c
  23. root@libin:~/program/C/testlib/head# ./test
  24. tid = 3077905264
  25. hello
  26. hello

    有一些警告,但是毕竟编出了可执行文件test,并能够正确的执行。

    我们的实验做完了,没有包含任何头文件,我想做的事情,动态库也帮我做了。这个实验就证明了,头文件并不是必不可少的,没有头文件,只要你清楚函数调用的接口,清楚需要包含那个库,一样可以做你想做的事情。

    很颠覆常规是吧,既然没有头文件,我也能干事,头文件存在还有什么意义呢?

    我们想一下,如果我写了一个功能很强大的库,(静态库或者动态库,whatever),完全没有头文件,因为函数通通是我写的,我很清楚每个函数的入参个数 类型及返回值,清楚每个函数的功能,所以我可以不需要头文件。但是如果另外一个人来用我的库文件,他就会很挠头,因为没有头文件,他不知道我库里面有那些函数,每个函数入参 个数类型以及返回值。他就没法调用我的库做想做的事情。从这个角度上讲,头文件是描述性的文件,它不涉及功能的实现,就好像一本书,没有任何的目录和章节名,也是OK的,但是读者读起来就很不方便。

    除了这一点,我还想到了一点,就是让编译器帮我们做必要的检查。编译器的功能是很强大的,他能帮你做很多的事情。如果你没有包含头文件,函数调用错了,编译器也帮不了你。看下面的例子:

    我将sleep函数调用多加了个参数,pthread_create函数少传了个参数,因为我没有头文件,所以编译器帮不了什么。

  1. int f()
  2. {
  3.         while(1)
  4.         {
  5.                 printf("hello\n");
  6.                 sleep(5,4);
  7.         }
  8. }


  9. int main()
  10. {
  11.         creat("testfile",0777);
  12.         // printf("hello world\n");
  13.         unsigned long tid = 0;
  14.         int ret = pthread_create(&tid,0,&f);
  15.         if(ret)
  16.         {
  17.                 printf("pthread_create failed \n");
  18.         }
  19.         else
  20.         {
  21.                 printf("tid = %lu\n",tid);
  22.                 pthread_join(tid,0);
  23.         }


  24.         return 0;
  25. }

  1. root@libin:~/program/C/testlib/head# gcc -Wall -o test test.c -lpthread
  2. test.c: In function ‘f’:
  3. test.c:6: warning: implicit declaration of function ‘printf’
  4. test.c:6: warning: incompatible implicit declaration of built-in function ‘printf’
  5. test.c:7: warning: implicit declaration of function ‘sleep’
  6. test.c: In function ‘main’:
  7. test.c:14: warning: implicit declaration of function ‘creat’
  8. test.c:17: warning: implicit declaration of function ‘pthread_create’
  9. test.c:20: warning: incompatible implicit declaration of built-in function ‘printf’
  10. test.c:24: warning: incompatible implicit declaration of built-in function ‘printf’
  11. test.c:25: warning: implicit declaration of function ‘pthread_join’
    看到了,编译器gcc爱莫能助,发现不了sleep多了个参数,pthread_create少个参数。这种情况下还是能运行。

  1. root@libin:~/program/C/testlib/head# ./test
  2. tid = 3079175024
  3. hello
  4. hello
  5. hello
  6. hello
  7. hello
  8. hello
  9. hello
    这种情况是很危险的,因为会破坏栈空间,有可能将栈空间踩坏。但是如果我包含了头文件,就不同了,编译器会帮我检查。

  1. #include<unistd.h>
  2. #include<pthread.h>

  3. int f()
  4. {
  5.         while(1)
  6.         {
  7.                 printf("hello\n");
  8.                 sleep(5,4);
  9.         }
  10. }


  11. int main()
  12. {
  13.         creat("testfile",0777);
  14.         // printf("hello world\n");
  15.         unsigned long tid = 0;
  16.         int ret = pthread_create(&tid,0,&f);
  17.         if(ret)
  18.         {
  19.                 printf("pthread_create failed \n");
  20.         }
  21.         else
  22.         {
  23.                 printf("tid = %lu\n",tid);
  24.                 pthread_join(tid,0);
  25.         }


  26.         return 0;
  27. }
    看下编译情况:

  1. root@libin:~/program/C/testlib/head# ll
  2. 总用量 12
  3. drwxr-xr-x 2 root root 4096 2012-07-28 10:51 ./
  4. drwxr-xr-x 5 root root 4096 2012-07-27 19:05 ../
  5. -rw-r--r-- 1 root root 444 2012-07-28 10:51 test.c
  6. root@libin:~/program/C/testlib/head# gcc -Wall -o test test.c -lpthread
  7. test.c: In function ‘f’:
  8. test.c:8: warning: implicit declaration of function ‘printf’
  9. test.c:8: warning: incompatible implicit declaration of built-in function ‘printf’
  10. test.c:9: error: too many arguments to function ‘sleep’
  11. test.c: In function ‘main’:
  12. test.c:16: warning: implicit declaration of function ‘creat’
  13. test.c:19: warning: passing argument 3 of ‘pthread_create’ from incompatible pointer type
  14. /usr/include/pthread.h:227: note: expected ‘void * (*)(void *)’ but argument is of type ‘int (*)()’
  15. test.c:19: error: too few arguments to function ‘pthread_create’
  16. test.c:22: warning: incompatible implicit declaration of built-in function ‘printf’
  17. test.c:26: warning: incompatible implicit declaration of built-in function ‘printf’
    有种观点是这样的,如果你的程序可能崩溃,请让它在第一时间第一现场崩溃,这样调试的代价比较低,方便troubleshooting,如果程序已经走到了背离预想轨道的分支,但是还有办法不让程序崩溃,请不要做这种事情,因为,早就背离了轨道,你不去检查,硬让程序继续走下去,如果终于崩溃了,你找不到这个第一现场,你很难调试。

    编译检查也是这样,对自己的代码严格检查,让它编译不过也比编译过了埋下隐患要好的多。因为越往后面,troubleshooting的代价也就越大。

参考文献:
1 百度百科 头文件词条



阅读(25246) | 评论(13) | 转发(3) |
给主人留下些什么吧!~~

GFree_Wind2012-07-30 12:37:15

关于函数声明,我记得编译链接的时候,在找不到函数声明的时候,会根据上下文推测函数的签名。这样的话,即使你在使用中传入正确的参数个数和类型,依然有可能引起很晦涩的问题。比如返回值,比如类型转换等。

GFree_Wind2012-07-30 12:35:23

hml1006: lz写一个小程序,然后gcc -E xx.c看一下预处理,就知道头文件干什么的了。说白了,c和h文件会被预处理程序写到同一个文件,然后在编译这个文件.....
Bean_lee肯定是知道这个的。他是在做进一步的研究

hml10062012-07-30 10:24:21

lz写一个小程序,然后gcc -E xx.c看一下预处理,就知道头文件干什么的了。说白了,c和h文件会被预处理程序写到同一个文件,然后在编译这个文件