西安联嵌吧 关注:3贴子:161
  • 8回复贴,共1

如何引用一个已经定义过的全局变量?

只看楼主收藏回复

如何引用一个已经定义过的全局变量?


1楼2014-09-21 20:55回复
    解答:
    如果要引用的全局变量在同一文件内定义,则可以直接引用;如果要引用的全局变量在另外的C文件中定义,则有两种引用方式。第一种是使用#include包含声明了该全局变量的头文件,第二种是使用extern关键字在本文件中再次声明该全局变量。


    3楼2014-09-21 20:55
    回复
      2025-07-14 21:09:51
      广告
      深度解析:
      C语言中有定义和声明,需搞清楚两者的联系和区别。变量定义的本质是新建一个变量(或者向系统申请一个变量),系统需要为新建立的变量分配内存空间。因此变量的定义意味着变量的生成;而声明却不产生新的变量,只是告诉编译器有这么一个变量存在(也许是在别的地方定义的)。变量的定义好理解,因此要搞清楚两者的区别关键在于理解声明。
      首先要明白为什么需要声明(我相信没有人想问为什么需要定义吧···)。这是个很深刻的问题,要理解这个问题需要从编译连接系统的工作原理入手。C语言的编译系统工作方式是:


      4楼2014-09-21 20:56
      回复
        第一阶段:单个.c源文件先独立分开编译(生成对应的.o目标文件),由编译器完成,此阶段发现的错误称为编译错误。如语法错误、变量未定义等都是编译错误。
        第二阶段:所有的.o目标文件连接生成可执行程序,由连接器完成。此阶段发现的错误成为连接错误。如某个符号未定义,多重定义等。
        (注:实际的过程可能更复杂,譬如要考虑预处理器、汇编器的工作,连接阶段要考虑预编译库等等。这里为了简单起见,大家先不管这些细节了。)
        编译时,变量必须先经过声明才能使用。没有声明就直接使用的变量会被判编译错误(原因是编译器需要变量或者函数的类型原型声明以判断类型不匹配错误)。也就是说编译阶段编译器只看声明而不要求定义。


        5楼2014-09-21 20:56
        回复
          连接时,同名变量必须有且仅有一次定义。变量只有声明而没有定义,或者多处定义同名变量都会报连接错误。因为同名变量(也就是同一个变量)只能分配一处内存空间,因此只能定义一次。
          好了,废了半天口舌,背景基本交代清楚了。现在我们假设这样一种情况:由两个文件a.c和b.c组成的工程,在a.c中定义了全局变量var,同时在a.c和b.c中都使用到该变量。这种情况实际上很常见,我相信大家都遇到过。
          此时,a.c中引用var很容易。只要将var定义在a.c的最前面部分,即可保证文件中任何函数都可以无障碍访问var,保证编译和连接阶段都不会报错。但是b.c呢?我们在b.c中如何使用var呢?下面来逐步分析一下:
          <思路1>:在b.c中再次定义var。这种方法可以吗?如果可以那我们在b.c中定义的var和a.c中的是同一个吗?根据C语言的规定,变量定义意味着创造一个新的变量,因此这两个var虽然同名,但肯定是两个不同的变量。因此不能满足我们的需要。思路1失败。


          6楼2014-09-21 20:57
          回复
            <思路2>:在头文件a.h中声明var,然后在b.c中#include。这种方法行吗?分析编译过程看看。a.c编译肯定不会有错,b.c编译时因为包含了a.h中var的声明,因此编译器得到了var的变量原型(即编译器知道有一个变量名叫var,并且知道这个var的类型信息等,但编译器不知道这个var在哪里定义的。不过编译器根本不需要知道这个变量到底在哪里定义的,交给连接器同志去处理这些吧!),因此只要在b.c中使用该var的地方符合a.h中var的声明,b.c即可顺利通过编译。至此两个源文件编译通过,进入第二阶段连接。连接器在连接时会为b.o中引用var的部分寻找var的定义体。此时连接器很轻松的发现a.o中即有一个名为var的全局变量的定义体,因此连接器确定了a.o和b.o中的var为同一个变量。至此问题圆满解决。思路2成功。
            <思路3>:使用extern关键字。a.c中情况不变,仍然定义全局变量var(例如,int var;),b.c中使用extern关键字声明变量var(extern int var;)。注意没有使用头文件中的声明,这样可以吗?同样的,先分析a.c和b.c各自的编译过程,再分析连接过程。编译时a.c当然没问题,b.c中因为先使用extern int var;对var进行了声明(声明的意义和思路2中相同)因此编译阶段也没问题。连接阶段和思路2相同,因此连接成功。至此,思路3成功。


            7楼2014-09-21 20:57
            回复
              通过以上分析,我们知道引用包含声明的头文件或者extern声明的方式都可以使一个源文件引用其他源文件内定义的变量。下面来探讨一些更细节更有趣的问题。
              <问题1>:变量能否定义在头文件内?如果可以,那是定义在头文件中好还是定义在源文件中好?
              实际测试证明,变量是可以定义在头文件内的,测试示例见章节示例test2。那么是不是说变量定义放在源文件与头文件是随意的,没有任何区别呢?请再看章节示例test3。此示例中我们在a.h中定义了全局变量var1和var2,然后在a.c和b.c中都include了该头文件,结果链接时报错,提示var2重复定义了。
              test3很好的演示了变量定义在头文件中的坏处。当该头文件被多个源文件引用时即会导致该变量被重复定义,造成链接时错误。所以,变量还是定义在源文件中的好。


              8楼2014-09-21 21:46
              回复
                <问题2>:工程中包含两个源文件a.c和b.c,a.c中有一行int var,并且在其后的函数中引用了该var; b.c中也有一行int var并且在其后的函数中也使用了var;该工程能否成功编译连接。如果不能,请指出编译还是连接错误,哪个文件会报错?
                在test4中,我们用一个实例说明了以上的定义是允许的,而且a.c和b.c中定义的同名变量var实际上是同一个变量。怎么回事?为什么没有重复定义呢?


                9楼2014-09-21 21:46
                回复
                  2025-07-14 21:03:51
                  广告
                  大家回忆下test1中对变量var的定义和声明,可以发现其实变量的定义和声明形式上是相同的,有时编译器会将它看作定义,有时会将它看作声明,有时候又是定义加声明,编译器会很智能的处理这个问题(当然了如果使用了赋值运算符=在定义的同时初始化,那这个表达式就一定是定义而肯定不是声明了)。譬如test4的示例中,a.c和b.c中都有int var;的表达式,所以编译器会自动将其中一个当作定义,而另一个当作声明。因此,就算你再多加几十个源文件都使用这个同名变量,还是只会有一个是定义,其余全是声明,不会报错。
                  那如果我们在定义变量的同时给其赋初值呢?如test5中的样子。会发现出现链接错误,var重复定义了。原因很简单,因为我们对var的两个定义表达式都赋初值了,所以编译器不得不把两个表达式都当成定义,所以连接时会重复定义。
                  代码见附件


                  10楼2014-09-21 21:46
                  回复