通过项目实践带你彻底搞懂C/C++编程中static与extern两关键字的使用

目录

1、概述

2、编译C++代码时遇到的变量及函数重复定义的问题

3、用 extern 声明外部变量

4、extern与全局函数

5、为何在变量和函数前添加一个static关键字编译就没问题了呢?

6、静态局部变量

7、函数的声明与定义都放置到一个头文件中,不使用static,通过宏控制去实现


       最近在帮同事排查一个与exrern、static相关的C++代码编译错误时,遇到了一些对基础C语言基础知识理解不到的问题。说来惭愧,虽然写了多年的C/C++代码,但对extern、static两关键字的使用与理解一直不够精准,于是借这次遇到的代码编译问题,详细地查阅了extern和static相关的内容,结合之前的项目代码,才彻底搞清楚这两个关键字相关的内容,今天在这里给大家分享一下。

1、概述

       static和exrern是C语言中的关键字,C++语言中在处理C++类时做了一定的延伸。extern用来声明外部全局变量,static可以用来声明变量、全局函数及C++类的静态函数。

       这次遇到这个编译的问题,我特意翻看了谭浩强老师的<>一书,书中对extern和static两关键字有详细的说明。有多年开发经验后再回过头去看这本书,才理解很多上学时理解不了的内容,才感叹书中对extern和static关键字的表述是多么的精准到位,也许这就是这本书最经典的原因吧!现在回过头去看,基础知识确实比较重要啊!正是这本书中关于extern和static精准到位的表述,才破解了我们心头的疑惑,顺利地解决了这次遇到的编译问题。

        下面我们就来通过这个问题来详细讲述一下static和exrern两关键字的相关内容。

2、编译C++代码时遇到的变量及函数重复定义的问题

       同事在一个头文件中增加了一个外部变量g_szLogFileName,如下所示:

通过项目实践带你彻底搞懂C/C++编程中static与extern两关键字的使用_第1张图片

想把这个外部变量当成全局变量来使用,结果启动编译后报出了该变量重复定义的错误:

通过项目实践带你彻底搞懂C/C++编程中static与extern两关键字的使用_第2张图片

在一个头文件中定义一个变量,在多个cpp中多次包含这个头文件,则会提示变量重复定义的问题。

       后来看到下面的WriteWbLog接口也在头文件中,这个头文件被多个cpp包含(就像将代码拷贝过去编译一样),为啥这个接口没有报多次被定义的问题呢?难道是因为WriteWbLog函数前面有个static关键字?于是手动在g_szLogFileName变量前添加一个static,结果编译就不再报错了。如果将WriteWbLog函数前面的static去掉,编译就会报错:

通过项目实践带你彻底搞懂C/C++编程中static与extern两关键字的使用_第3张图片

提示WriteWbLog函数多次被定义的问题:

LNK2005 "void __cdecl WriteWbLog(wchar_t const *,...)" (?WriteWbLog@@YAXPB_WZZ) 已经在 entitytracker.obj 中定义            E:\XXXXXX\CElectronicPenCore.obj     

这里面牵涉到C语言中的几个概念:
1)外部变量与全局变量的区别;
2)C语言中static的作用;
3)C语言中extern关键字的作用。

3、用 extern 声明外部变量

      关于extern关键字,谭浩强老师的<>中有一段精准而到位的表述:

通过项目实践带你彻底搞懂C/C++编程中static与extern两关键字的使用_第4张图片

       外部变量(即全局变量)函数外部定义的变量,它不属于哪一个函数,它属于一个源程
序文件。其作用域是整个源程序。外部变量实在函数外部定义的,它的作用域为从变量定义处开始,到本程序文件的末尾。如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。如果在定义点之前的函数想引用该外部变量,则应该在引用之前用关键字 extern对该变量作“外部变量声明”。表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
        文中给出了示例代码:用 extern 声明外部变量,扩展程序文件中的作用域。

int max(int x,int y)
{
    int z;
    z = x > y ? x : y;
    return(z);
}

int main()
{
    extern A,B; 
    printf("%d\n",max(A,B));
    return 0;
}

int A=13, B=-8;

假设这段代码放置在cpp中,int型变量A和B在cpp文件结尾定义的,它们的组用域从它们定义处到cpp文件结束。如果不在main函数中用extern声明一下这两个变量,因为不再这两个变量的作用域内,所以不能直接使用的。

       如果定义的全局变量需要在多个cpp文件中使用,需要在一个公用的头文件用extern关键字声明一下这些全局变量,使用到这些全局变量时,只需要包含这个头文件即可。比如在VS创建的工程中一般都有个stdafx.h头文件,所有的cpp文件都需要包含这个头文件,所以可以在stdafx.h头文件中使用extern声明全局变量,也可以在一个公用的头文件(比如common.h)使用extern去声明,然后在使用到全局变量的cpp文件中包含stdafx.h头文件或者公用头文件(比如common.h)就可以了。

比如在头文件中使用extern声明:
extern BOOL g_bFileLogOn;
在cpp文件中对变量进行定义并初始化:
BOOL g_bFileLogOn = FALSE;

       头文件可能会直接被多个cpp文件包含,或者间接的被多个cpp文件包含(比如在file-a.h头文件使用extern声明了全局变量,然后file-b.h中包含了file-a.h头文件,然后多个cpp文件包含了file-b.h头文件,即file-a.h被多次间接包含),所以会出现多次被extern声明的情况,声明多次是可以的,但定义只能有一次,否则就是重复定义了。

4、extern与全局函数

       对于全局函数, extern关键词的声明是可有可无的,因为函数本身不加修饰的话就是extern的。在引用全局函数时需要函数声明,可以直接声明函数,也可以使用#include去包含函数声明的头文件,比如:

int AddNum( int a, int b)

int main()
{
extern A,B; 
printf("%d\n",max(A,B));
    return 0;
}

int AddNum( int a, int b)
{
    int nSum = a + b;
    retrun nSum;
}

       一般我们公用函数或者使用频繁的函数封装到一个.h文件和.cpp中,函数的声明放置在.h头文件中,函数的实现放在.cpp,要使用全局函数,只要包含对应的.h头文件即可。

5、为何在变量和函数前添加一个static关键字编译就没问题了呢?

       在 C 语言中,对于被声明为 static 的全局实体(包括变量和函数),在声明它的文件之外是不可见的。这句话来自于C++ Primer一书,如下所示:

通过项目实践带你彻底搞懂C/C++编程中static与extern两关键字的使用_第5张图片

在C语言编程中,static的一个作用就是信息屏蔽。比方说,你自己定义了一个文件,该文件中有一系列的函数以及变量的声明和定义,你希望该文件中的一些函数和变量只能被该文件中的函数使用,那么,你可以在该函数、变量的前面加上static,代表他们只能被当前文件中的函数使用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。所以,在不同文件中定义同名的staitc函数是没问题的,不会冲突的。

       对于本例中,如果在变量和函数前添加一个static修饰,就不会再提示变量重复定义的问题。因为static限定的变量和函数只在包含头文件的cpp中是可见的,在其他cpp中不可见,所以多个cpp包含这个头文件多次去定义变量和函数,因为相互之间不可见,所以没有出现重复定义的问题。

6、静态局部变量

         关于静态局部变量,谭浩强老师的<>中也有一段表述:

通过项目实践带你彻底搞懂C/C++编程中static与extern两关键字的使用_第6张图片

有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为“静态局部变量”,用关键字 static 进行声明。在函数多次重入的情况下,该变量一直存在,且是有值的。
       比如,实现打印 1 到 5 的阶乘值,对应的代码实现如下:

int fac(int n)
{
    static int f=1;
    f=f*n;
    return(f);
}

main()
{
    int i;
    for(i=1;i<=5;i++)
       printf("%d!=%d\n",i,fac(i));
}

对静态局部变量的说明:

1) 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放。
2))静态局部变量在编译时赋初值,即只赋初值一次;而对自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
3) 如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值 0(对
数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。

7、函数的声明与定义都放置到一个头文件中,不使用static,通过宏控制去实现

       我们也可以将函数声明和函数实现放在一个头文件中,文件中分成两部分,一部分是函数声明,一部分是函数实现,函数实现部分通过宏去控制,如下所示:

通过项目实践带你彻底搞懂C/C++编程中static与extern两关键字的使用_第7张图片

所有调用到头文件中的全局函数的地方包含这个头文件,用于编译。然后在任意一个cpp中定义头文件中的宏:

#define HIS_CFG_FILE_DIR_CPP
#include "hiscfgdir.h"

然后包含头文件,保证在编译后obj文件中能有这些函数的实现,用于链接。只要在一个cpp文件中定义上述宏,如果在多个cpp中都定义这个宏,则会出现函数重复定义的问题。

你可能感兴趣的