linux extern关键字的作用



  1.     1.简单的例子。

     

         //包含头文件是为了声明这个函数,表示这个函数已经被定义过了,已经生产过二进制代码了。

    File.c代码   收藏代码
    1. #include <stdio.h>  
    2.   
    3. int main(int argc, char **argv)  
    4. {  
    5.     printf("hello world!");  
    6.     return 0;  
    7. }  

     

     

               

     

      假设现在是一个大工程,A.c include B.h  C.h,现在又有B.c  C.c文件。编译过程如何呢?

     

      编译是单个文件进行的。这句话如何理解呢?

     

       计算机最终执行的是二进制代码,我们用的库都是二进制机器码,CPU不停地吃,来执行指令。假设我们写好了许多

     

     许多好用的函数,从文本文件(main.c)编译成了二进制机器码文件(main.so,即库文件)。现在我需要利用这些基础的

     

    函数形成重新组合调用,获得一些新的功能。比方说,我又添加了extra.c 来调用main.c中的函数。这时候就有了两种办法来生成新的二进制文件。

     

    一种就是把main.c extra.c作为一个整体的whole.c来编译,生成whole.so二进制文件。从方法学的角度讲,这是整体

     

    法。当然这是个不好的方法。聪明的办法分而治之,划分,化整体为局部。先把后来的extra.c编译好了之后,再整体化处

     

    理,这个额外的过程就是链接。因为编译好的二进制代码是没必要重新生成的,只需要新的二进制代码执行后,跳入到编译

     

    好的二进制代码那里执行就好了。

     

        又回到我们假设的工程,现在有三个文件,A.c, B.c, C.c,我们可以一个个编译成A.o, B.o, C.o,因为A调用了B,C,

     

    所以最后的连接过程是不会丢失这个调用的关系的。链接的过程就像是声明一种边界一样。我在这里声明:这个是编译好的

     

    文件,你不需要编译,你自己管你自己的就行了。最后连接器会把这个边界给缝合起来的。

     

     

      在例子中,我们看到,我们include 了<stdio.h>,这里声明了好多函数,就是告诉编译器,操作系统中这些函数都已经

     

    实现了,(至于在哪里,我们最后再谈。),所以这个printf是合法的,然后我们用gcc -c file.c ,可以发现生成了file.o。

     

    file.o 包含了printf的一个符号地址,用链接程序再替换一下,换成物理地址,gcc file.c -o file.exe,file.exe就包含

     

    真正的物理地址了,就可以去call库文件中的函数了。这样程序就会执行了。

     

        同理,我们再试试这样写:

     

       extern int printf (__const char *__restrict __format, ...);

    C代码   收藏代码
    1. //printf这个函数是在库文件(libc.so.6 => /lib/tls/i686/cmov/libc.so.6)  
    2. //中已经被编译成了二进制代码  
    3. int main(int argc, char **argv)  
    4. {  
    5.     printf("hello world!");  
    6.     return 0;  
    7. }  
     

       我们用gcc -c file.c可以看到命令是可以成功的。这里的extern也就是相当于#include <stdio.h>一样,告诉编译器一个

     

    边界,这个,我是有二进制代码的!!(在某某二进制的库文件里面) 放过他,请继续编译。如果我们既没有头文件,也没

     

    声明这个是个"已经编译好的二进制函数“(extern 可以看成是个 ”二进制函数“,我自己发明的,不正规)。编译一下如何?

     

    结果是这个: 

     

        file.c:7: warning: incompatible implicit declaration of built-in function ‘printf’

     

    编译会给个警告,自作聪明地给你反推出一个”二进制“函数,自己假设这个函数已经有了,然后连接搜索库,结果真找了,

     

    呵呵。但是它的反推功能如何,我不知道。

     

        再假设,我们自己的函数,调用了一个sayhello(),结果sayhello()这个函数根本就没变成”二进制“函数,所以肯定会爆

     

    一个函数没有的错误。

     

        看到这里,我们已经大概明白了编译的过程。这是一个万丈高楼平地起的过程,一个个函数被”二进制“化,一个个新的再

     

    依赖这些”二进制函数“ 。库的过程就是这样,常用的所有基本功能编译好了,打成包,就成了库文件。新的开发依赖这个库

     

    ,所以我必须有这些库,而后,我自己再把自己的函数编译,打包,归档,形成了新的库,这样就形成了库的依赖关系。和

     

    java的jar包是一个道理。

     

     从这里,我们也可以得出一个结论:头文件是开发的非必要条件。因为可以不include,直接手工声明,也是可以编译,

     

    链接成功了。(头文件只是起到了一个方便的作用)。

     

    如果你要用C++开发mysql, 如果有合适的库文件,不需要它的头文件,也是可以开发的呦,不过我没试,

     

    所以不知道能不能成功,呵呵。注:(完整版的mysql,都是由开发包的,头文件,库文件一应俱全)。

     

    最用我们用ldd明来看看都依赖了哪些库?

     

     

      bard@bard-desktop:~/sharing$ ldd file.exe

    C代码   收藏代码
    1. linux-gate.so.1 =>  (0x002fe000)  
    2. libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00a5d000)  
    3. /lib/ld-linux.so.2 (0x00c56000)  
    4. ard@bard-desktop:~/sharing$   
     

    最后,我们做一下拓展训练:

     

      首先只有一个文件:

     

    Java代码   收藏代码
    1. int main(int argc, char **argv)  
    2. {  
    3.     sayHello();  
    4.     return 0;  
    5. }  
     

     

        gcc -c file.c ,我们看到是不成功的。

     

     

        第二个文件

           #include <stdio.h>

    Java代码   收藏代码
    1. int one=1;  
    2. int two=2;  
    3.   
    4. void sayHello()  
    5. {  
    6.     printf("love you");  
    7.       
    8. }  
     

     

        gcc -c constant.c  是可以成功的,生成了constant.o(似乎sayHello函数已经二进制化了)

     

     

        但是这时gcc file.c -o file.exe,还是不成功,为什么呢?

     

     

        看来这个constant.o 和libc.so库文件是有区别的啊,如果我们把这个constant.o打包到libc.so中就可以了。呵呵

     

       正确做法是

     

       gcc constant.o file.c -o file.exe

     

       看来这个命令的含义是:  编译file.c 并和constant.o中的”二进制“函数sayHello进行链接。

     

       不知道,各位兄弟姐妹们是如何理解编译过程的,如果您有好的想法,请给我留言。

     

     

     


  2. extern int O_RDONLY;  
  3.   
  4. #include <stdio.h>  
  5. //包含include <fcntl.h>时,会报错  
  6. /* 
  7. main.c:5: error: conflicting types for 'open' 
  8. /usr/include/fcntl.h:72: error: previous declaration of 'open' was here 
  9. */  
  10. #include<fcntl.h>  
  11. //#include <sys/stat.h>  
  12.   
  13. int open( const char * pathname, int flags)  
  14. {  
  15.     printf("string is %s\n",pathname);  
  16.     return 0;  
  17. }  
  18. int main(int argc, char **argv)  
  19. {  
  20.       
  21.     printf("flag is %d",O_RDONLY);  
  22.     //open("my lady gaga",34);  
  23.     return 0;     
  24. }  
  25.   
  26. //我们在这里故意写和fcntl.h中定义的函数 int open( const char * pathname, int flags);一致  
  27. //看看为什么在不包含fcntl.h时不报错,但是包含这个fcntl.h头文件时报错  
  28. //why, because 编译器链接范围的问题!  
  29. //c语言编译是个单个文件编译过程。  
  30. //以现在main.c为中心,这是个闭包,include的符号(函数,变量,包括include<头文件>,extern声明  
  31. //为外部符号,在链接时用到。  
  32.   
  33. //1.当有extern int O_RDONLY时,如果没有include<fcntl.h>,那么会报未定义的错误,  
  34. //增加则不会  
  35.   
  36. //2.如果有int open函数,如果没有 include <fcntl.h>是不会报错的,有会报错  
Java代码   收藏代码
  1.   
Java代码   收藏代码
  1. 这些错误的猜想:应该归于宏定义的问题,而不是链接的问题。应该连接器还是知道去哪里链接的,但是由于宏定义的  
Java代码   收藏代码
  1. 问题,使其出错。  

    1.简单的例子。

 

     //包含头文件是为了声明这个函数,表示这个函数已经被定义过了,已经生产过二进制代码了。

File.c代码   收藏代码
  1. #include <stdio.h>  
  2.   
  3. int main(int argc, char **argv)  
  4. {  
  5.     printf("hello world!");  
  6.     return 0;  
  7. }  

 

 

           

 

  假设现在是一个大工程,A.c include B.h  C.h,现在又有B.c  C.c文件。编译过程如何呢?

 

  编译是单个文件进行的。这句话如何理解呢?

 

   计算机最终执行的是二进制代码,我们用的库都是二进制机器码,CPU不停地吃,来执行指令。假设我们写好了许多

 

 许多好用的函数,从文本文件(main.c)编译成了二进制机器码文件(main.so,即库文件)。现在我需要利用这些基础的

 

函数形成重新组合调用,获得一些新的功能。比方说,我又添加了extra.c 来调用main.c中的函数。这时候就有了两种办法来生成新的二进制文件。

 

一种就是把main.c extra.c作为一个整体的whole.c来编译,生成whole.so二进制文件。从方法学的角度讲,这是整体

 

法。当然这是个不好的方法。聪明的办法分而治之,划分,化整体为局部。先把后来的extra.c编译好了之后,再整体化处

 

理,这个额外的过程就是链接。因为编译好的二进制代码是没必要重新生成的,只需要新的二进制代码执行后,跳入到编译

 

好的二进制代码那里执行就好了。

 

    又回到我们假设的工程,现在有三个文件,A.c, B.c, C.c,我们可以一个个编译成A.o, B.o, C.o,因为A调用了B,C,

 

所以最后的连接过程是不会丢失这个调用的关系的。链接的过程就像是声明一种边界一样。我在这里声明:这个是编译好的

 

文件,你不需要编译,你自己管你自己的就行了。最后连接器会把这个边界给缝合起来的。

 

 

  在例子中,我们看到,我们include 了<stdio.h>,这里声明了好多函数,就是告诉编译器,操作系统中这些函数都已经

 

实现了,(至于在哪里,我们最后再谈。),所以这个printf是合法的,然后我们用gcc -c file.c ,可以发现生成了file.o。

 

file.o 包含了printf的一个符号地址,用链接程序再替换一下,换成物理地址,gcc file.c -o file.exe,file.exe就包含

 

真正的物理地址了,就可以去call库文件中的函数了。这样程序就会执行了。

 

    同理,我们再试试这样写:

 

   extern int printf (__const char *__restrict __format, ...);

C代码   收藏代码
  1. //printf这个函数是在库文件(libc.so.6 => /lib/tls/i686/cmov/libc.so.6)  
  2. //中已经被编译成了二进制代码  
  3. int main(int argc, char **argv)  
  4. {  
  5.     printf("hello world!");  
  6.     return 0;  
  7. }  
 

   我们用gcc -c file.c可以看到命令是可以成功的。这里的extern也就是相当于#include <stdio.h>一样,告诉编译器一个

 

边界,这个,我是有二进制代码的!!(在某某二进制的库文件里面) 放过他,请继续编译。如果我们既没有头文件,也没

 

声明这个是个"已经编译好的二进制函数“(extern 可以看成是个 ”二进制函数“,我自己发明的,不正规)。编译一下如何?

 

结果是这个: 

 

    file.c:7: warning: incompatible implicit declaration of built-in function ‘printf’

 

编译会给个警告,自作聪明地给你反推出一个”二进制“函数,自己假设这个函数已经有了,然后连接搜索库,结果真找了,

 

呵呵。但是它的反推功能如何,我不知道。

 

    再假设,我们自己的函数,调用了一个sayhello(),结果sayhello()这个函数根本就没变成”二进制“函数,所以肯定会爆

 

一个函数没有的错误。

 

    看到这里,我们已经大概明白了编译的过程。这是一个万丈高楼平地起的过程,一个个函数被”二进制“化,一个个新的再

 

依赖这些”二进制函数“ 。库的过程就是这样,常用的所有基本功能编译好了,打成包,就成了库文件。新的开发依赖这个库

 

,所以我必须有这些库,而后,我自己再把自己的函数编译,打包,归档,形成了新的库,这样就形成了库的依赖关系。和

 

java的jar包是一个道理。

 

 从这里,我们也可以得出一个结论:头文件是开发的非必要条件。因为可以不include,直接手工声明,也是可以编译,

 

链接成功了。(头文件只是起到了一个方便的作用)。

 

如果你要用C++开发mysql, 如果有合适的库文件,不需要它的头文件,也是可以开发的呦,不过我没试,

 

所以不知道能不能成功,呵呵。注:(完整版的mysql,都是由开发包的,头文件,库文件一应俱全)。

 

最用我们用ldd明来看看都依赖了哪些库?

 

 

  bard@bard-desktop:~/sharing$ ldd file.exe

C代码   收藏代码
  1. linux-gate.so.1 =>  (0x002fe000)  
  2. libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00a5d000)  
  3. /lib/ld-linux.so.2 (0x00c56000)  
  4. ard@bard-desktop:~/sharing$   
 

最后,我们做一下拓展训练:

 

  首先只有一个文件:

 

Java代码   收藏代码
  1. int main(int argc, char **argv)  
  2. {  
  3.     sayHello();  
  4.     return 0;  
  5. }  
 

 

    gcc -c file.c ,我们看到是不成功的。

 

 

    第二个文件

       #include <stdio.h>

Java代码   收藏代码
  1. int one=1;  
  2. int two=2;  
  3.   
  4. void sayHello()  
  5. {  
  6.     printf("love you");  
  7.       
  8. }  
 

 

    gcc -c constant.c  是可以成功的,生成了constant.o(似乎sayHello函数已经二进制化了)

 

 

    但是这时gcc file.c -o file.exe,还是不成功,为什么呢?

 

 

    看来这个constant.o 和libc.so库文件是有区别的啊,如果我们把这个constant.o打包到libc.so中就可以了。呵呵

 

   正确做法是

 

   gcc constant.o file.c -o file.exe

 

   看来这个命令的含义是:  编译file.c 并和constant.o中的”二进制“函数sayHello进行链接。

 

   不知道,各位兄弟姐妹们是如何理解编译过程的,如果您有好的想法,请给我留言。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值