• extern C小结


     

    名词解释

    1.extern

      extern翻译为外部的,用在变量或函数之前,使其可见范围拓宽,这就是为啥叫extern吧。那它是用来干嘛的呢?当用在变量或函数之前,提示编译器到其他模块寻找其定义。

      举个例子:

      在源文件A里定义的函数,在其它源文件里是看不见的(即不能访问)。为了在源文件B里能调用这个函数,应该在B的头部加上一个外部声明:
         (extern)   函数原型;   
        这样,在源文件B里也可以调用那个函数了。  

    2.声明与定义

      声明(declaration)仅仅是声明有这么个东西,但是并没有涉及到内存分配等动作,这些动作在其他地方做,那么它是不是就没什么作用了呢,相反,它还是扮演了很重要的角色,首先它声明了一个变量或函数的类型,让程序知道某个变量的类型,或者一个函数的参数及返回值类型。

      定义(definition)可以理解为声明的超集,也可以把声明理解为定义的子集,定义的同时进行了内存的分配,所以声明可以进行多次,但定义就只能做一次。

    要点讲解

    1.extern 函数

      这个会比较简单,当你定义一个函数时,如

      Int fun(void)

      编译器会默认它为

      extern int fun(void)

      所以在函数之前加或者不加extern区别不大。

      从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int funint mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int funint mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这大概是KISS原则的一种体现吧!这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

    附上实例:

    //file_01.h
    
    int add(int, int);//显然这里是一个全局函数,这样才具有被extern调用的属性。
    
    //file_01.c
    
    #include "file_01.h"
    int add(int a, int b)
    {
      return a + b;
    }
    
    //main.c
    
    #include <stdio.h>
    int main(int argc, char** argv)
    {
    int a = 4;
    int b = 5;
    extern int add(int, int);//这一行可以放在调用之前的任何地方,extern也是可以省略的
    int c = add(a, b);
    printf("%d
    ", c);
    } 

    2.extern 变量

      简单点讲:

      extern int var;//这个就是一个声明,可以进行多次

      int var;//这个就是一个定义

      extern int var = 0;//这个不仅是一个声明,同时也是一个定义

      举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说a.c要引用到v,不只是取决于在a.c中声明extern int v,还取决于变量v本身是能够被引用到的。这涉及到c语言的另外一个话题--变量的作用域。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。

    附上实例:

    //file_01.h
    
    extern int g_var;
    /*此处就是一个声明,定义放在.c文件中实现,这样的做法就类似于函数了,只不过因为函数是默认带extern的,
    所以平时并没有注意而已,extern如果忽略,那么就相当于在.h文件就定义了,那么后面就直接g_var = 9,相当于赋值(不是初始化),
    不过有warning。*/
    
    //file_01.c
    
    #include "file_01.h"
    
    int g_var = 9;//此处是实际的定义了,并且初始化(应该不是赋值)。
    
    //main.c
    #include <stdio.h>
    
    extern int g_var;
    /*此处extern可以省略会比较神奇,所以省略之后仍然是声明
    可以做个简单验证,如果此处int g_var = 0;则会出现重定义错误。
    */
    
    int main(int argc, char** argv)
    {
    printf("%d
    ", g_var);
    }

    3.extern 数组& extern 指针

      一个源文件里定义了extern char a[] = “abcd”;

      可以在另一个文件里进行声明:extern char* a;吗?

    附上实例:

    //file_01.h
    
    extern char g_var[];
    
    //file_01.c
    
    #include "file_01.h"
    
    char g_var[] = "abcd";
    
     
    //main.c
    
    #include <stdio.h>
    
    //extern char* g_var;
    //如果用指针形式,运行时会出现段错误
    extern char g_var[];
    /*这里如果两行同时有效,则报错误:
    main.c:7: error: conflicting types for ‘g_var’
    main.c:6: error: previous declaration of ‘g_var’ was here
    从错误上来看,显然两次声明出现了类型冲突,说明编译器并没有把char*和char[]等同看待,
    不信把两行都声明成char*或者char[]就能顺利通过编译了。
    */
    
    int main(int argc, char** argv)
    {
     printf("%s
    ", g_var);
    }
     

      那么上面用指针形式报段错误到底是怎么回事呢?

    
    上面用指针形式,直接段错误,说明连接器根本就没把这里的g_var和file_01.c中定义的g_var当成一回事,
    它们是两种类型,奇怪的是,如果上述用指针形式,编译能通过,说明编译器认为g_var是已经定义过的变量,
    就是file_01.c中定义的那个,但是运行的时候段错误,到底咋回事呢,原来g_var直接被赋予了一个由数组中内容组成的值,
    因为原来这里的值是abcd,是被当成char[]来解释的,即g_var等于数组的首地址,而现在要被当成char*来解释了,所以很显然,
    那就等价于g_var = “abcd”,即g_var = 0x64636261,否则如果按正常的思路来讲应该是先对g_var初始化,然后再对其赋值,
    值为数组首地址地址,但是现在没有单独初始化变量g_var的动作,或者说数组初始化的时候这个变量就已经有了,
    说到底是数组和指针的内部结构不一样导致的。如果非要用这种指针形式来打印的话,也最多能打印数组中的前四个字节,
    因为指针变量的大小就是四个字节,可以这样打印
    
    unsigned db = (unsigned)g_var;
    char* ptr = (char*)(&db);
    printf("%s
    ", ptr);
    

    4.extern C

    C++中调用C库函数,就需要在C++程序中用extern C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

  • 相关阅读:
    JavaScript匿名函数的使用
    __construct __destory __call __get __set
    嵌入式学习
    动态加载script文件
    Android框架Volley使用:Post请求实现
    Android框架Volley使用:Get请求实现
    安卓开发笔记(三十五):Cardview的简单使用
    安卓开发笔记(三十四):Material Design框架实现优美的左侧侧滑栏
    Android APK反编译技巧全讲解
    Java数据结构(一):栈
  • 原文地址:https://www.cnblogs.com/seeken/p/5505466.html
Copyright © 2020-2023  润新知