`
jgsj
  • 浏览: 960444 次
文章分类
社区版块
存档分类
最新评论

C/C++定义全局变量/常量几种方法的区别

 
阅读更多

转自:http://wrchen.blog.sohu.com/71617539.html

在讨论全局变量之前我们先要明白几个基本的概念:

1. 编译单元(模块):
在IDE开发工具大行其道的今天,对于编译的一些概念很多人已经不再清楚了,很多程序员最怕的就是处理连接错误(LINK ERROR), 因为它不像编译错误那样可以给出你程序错误的具体位置,你常常对这种错误感到懊恼,但是如果你经常使用gcc,makefile等工具在linux或者嵌入式下做开发工作的话,那么你可能非常的理解编译与连接的区别!当在VC这样的开发工具上编写完代码,点击编译按钮准备生成exe文件时,VC其实做了两步工作,第一步,将每个.cpp(.c)和相应.h文件编译成obj文件;第二步,将工程中所有的obj文件进行LINK生成最终的.exe文件,那么错误就有可能在两个地方产生,一个是编译时的错误,这个主要是语法错误,另一个是连接错误,主要是重复定义变量等。我们所说的编译单元就是指在编译阶段生成的每个obj文件,一个obj文件就是一个编译单元,也就是说一个cpp(.c)和它相应的.h文件共同组成了一个编译单元,一个工程由很多个编译单元组成,每个obj文件里包含了变量存储的相对地址等 。

2. 声明与定义的区别
函数或变量在声明时,并没有给它实际的物理内存空间,它有时候可以保证你的程序编译通过, 但是当函数或变量定义的时候,它就在内存中有了实际的物理空间,如果你在编译模块中引用的外部变量没有在整个工程中任何一个地方定义的话, 那么即使它在编译时可以通过,在连接时也会报错,因为程序在内存中找不到这个变量!你也可以这样理解, 对同一个变量或函数的声明可以有多次,而定义只能有一次!

3. extern的作用
extern有两个作用,第一个,当它与"C"一起连用时,如: extern "C" void fun(int a, int b); 则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的, C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的"脾气"了(不同的编译器采用的方法不一样),为什么这么做呢,因为C++支持函数的重载啊,在这里不去过多的论述这个问题,如果你有兴趣可以去网上搜索,相信你可以得到满意的解释!
当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块活其他模块中使用,记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可, 在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。

如果你对以上几个概念已经非常明白的话,那么让我们一起来看以下几种全局变量/常量的使用区别:

1. 用extern修饰的全局变量
以上已经说了extern的作用,下面我们来举个例子,如:
在test1.h中有下列声明:
#ifndef TEST1H
#define TEST1H
extern char g_str[]; // 声明全局变量g_str
void fun1();
#endif
在test1.cpp中
#include "test1.h"

char g_str[] = "123456"; // 定义全局变量g_str

void fun1()
{
cout << g_str << endl;
}

以上是test1模块, 它的编译和连接都可以通过,如果我们还有test2模块也想使用g_str,只需要在原文件中引用就可以了
#include "test1.h"

void fun2()
{
cout << g_str << endl;
}
以上test1和test2可以同时编译连接通过,如果你感兴趣的话可以用ultraEdit打开test1.obj,你可以在里面着"123456"这个字符串,但是你却不能在test2.obj里面找到,这是因为g_str是整个工程的全局变量,在内存中只存在一份, test2.obj这个编译单元不需要再有一份了,不然会在连接时报告重复定义这个错误!
有些人喜欢把全局变量的声明和定义放在一起,这样可以防止忘记了定义,如把上面test1.h改为
extern char g_str[] = "123456"; // 这个时候相当于没有extern
然后把test1.cpp中的g_str的定义去掉,这个时候再编译连接test1和test2两个模块时,会报连接错误,这是因为你把全局变量g_str的定义放在了头文件之后,test1.cpp这个模块包含了test1.h所以定义了一次g_str,而 test2.cpp也包含了test1.h所以再一次定义了g_str, 这个时候连接器在连接test1和test2时发现两个g_str。如果你非要把g_str的定义放在test1.h中的话,那么就把test2的代码中#include "test1.h"去掉 换成:
extern char g_str[];
void fun2()
{
cout << g_str << endl;
}
这个时候编译器就知道g_str是引自于外部的一个编译模块了,不会在本模块中再重复定义一个出来,但是我想说这样做非常糟糕,因为你由于无法在test2.cpp中使用#include "test1.h", 那么test1.h中声明的其他函数你也无法使用了,除非也用都用extern修饰,这样的话你光声明的函数就要一大串,而且头文件的作用就是要给外部提供接口使用的,所以 请记住, 只在头文件中做声明,真理总是这么简单。

2. 用static修饰的全局变量
首先,我要告诉你static与extern是一对“水火不容”的家伙,也就是说extern和static不能同时修饰一个变量;其次,static修饰的全局变量声明与定义同时进行,也就是说当你在头文件中使用static声明了全局变量后,它也同时被定义了;最后,static修饰全局变量的作用域只能是本身的编译单元,也就是说它的“全局”只对本编译单元有效,其他编译单元则看不到它,如:
test1.h:
#ifndef TEST1H
#define TEST1H
static char g_str[] = "123456";
void fun1();
#endif

test1.cpp:
#include "test1.h"

void fun1()
{
cout << g_str << endl;
}

test2.cpp
#include "test1.h"

void fun2()
{
cout << g_str << endl;
}

以上两个编译单元可以连接成功, 当你打开test1.obj时,你可以在它里面找到字符串"123456", 同时你也可以在test2.obj中找到它们,它们之所以可以连接成功而没有报重复定义的错误是因为虽然它们有相同的内容,但是存储的物理地址并不一样,就像是两个不同变量赋了相同的值一样,而这两个变量分别作用于它们各自的编译单元。
也许你比较较真,自己偷偷的跟踪调试上面的代码,结果你发现两个编译单元(test1, test2)的g_str的内存地址相同,于是你下结论static修饰的变量也可以作用于其他模块,但是我要告诉你,那是你的编译器在欺骗你,大多数编译器都对代码都有优化功能,以达到生成的目标程序更节省内存,执行效率更高,当编译器在连接各个编译单元的时候,它会把相同内容的内存只拷贝一份,比如上面的"123456", 位于两个编译单元中的变量都是同样的内容,那么在连接的时候它在内存中就只会存在一份了, 如果你把上面的代码改成下面的样子,你马上就可以拆穿编译器的谎言:
test1.cpp:
#include "test1.h"

void fun1()
{
g_str[0] = 'a';
cout << g_str << endl;
}

test2.cpp
#include "test1.h"

void fun2()
{
cout << g_str << endl;
}

void main()
{
fun1(); // a23456
fun2(); // 123456
}

这个时候你在跟踪代码时,就会发现两个编译单元中的g_str地址并不相同,因为你在一处修改了它,所以编译器被强行的恢复内存的原貌,在内存中存在了两份拷贝给两个模块中的变量使用。

正是因为static有以上的特性,所以一般定义static全局变量时,都把它放在原文件中而不是头文件,这样就不会给其他模块造成不必要的信息污染,同样记住这个原则吧!

3 const修饰的全局常量

const修饰的全局常量用途很广,比如软件中的错误信息字符串都是用全局常量来定义的。const修饰的全局常量据有跟static相同的特性,即它们只能作用于本编译模块中,但是const可以与extern连用来声明该常量可以作用于其他编译模块中, 如
extern const char g_str[];
然后在原文件中别忘了定义:
const char g_str[] = "123456";

所以当const单独使用时它就与static相同,而当与extern一起合作的时候,它的特性就跟extern的一样了!所以对const我没有什么可以过多的描述,我只是想提醒你,const char* g_str = "123456" 与 const char g_str[] = "123465"是不同的, 前面那个const 修饰的是char * 而不是g_str,它的g_str并不是常量,它被看做是一个定义了的全局变量(可以被其他编译单元使用), 所以如果你像让char *g_str遵守const的全局常量的规则,最好这么定义const char* const g_str="123456".

/////////////////////////////////////////////////////////////////////////////////////////////////////////////

转自:http://blog.mcuol.com/User/isaleo/Article/7606_1.htm

变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量。

全局和静态变量都在堆里。(错了,全局变量和静态变量都在数据段,有些地方也叫静态存储区)

全局变量的作用范围是整个程序(如果程序是多个文件,必须在其他的文件中说明)。

静态变量的作用范围要看静态变量的位置,如果在函数里,则作用范围就是这个函数。

静态全局变量,只有本文件可以用。

全局变量是没有定义存储类型的外部变量,其作用域是从定义点到程序结束.省略了存储类型符,系统将默认为是自动型.

静态全局变量是定义存储类型为静态型的外部变量,其作用域是从定义点到程序结束,所不同的是存储类型决定了存储地点,静态型变量是存放在内存的数据区中的,它们在程序开始运行前就分配了固定的字节,在程序运行过程中被分配的字节大小是不改变的.只有程序运行结束后,才释放所占用的内存.

自动型变量存放在堆栈区中.堆栈区也是内存中一部分,该部分内存在程序运行中是重复使用的.

按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。

按作用域分,全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。

全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。

int a;/*全局*/
static int b;/*静态全局*/

int main()
{
}

这段代码如果是写在file.c里面的,那么file2.c就不能调用b;
但是file2.c中可以通过声明外部变量extent a;
从而调用a


C++中内存的分配:
1.栈:是用来存放像一般变量和函数参数等的一块内存。系统会在变量生存期结束时自动释放内存,即把内存从栈中弹出。
2.堆:用来存放动态变量,如指针。要通过设计者自己管理变量,自行进行创建和清理工作。(利用new和delete)
3.自由存储区:就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
4.全局/静态存储区:全局变量和静态变量被分配到同一块内存。
5.常量存储区:用来存储常量,即不能被修改的变量。
­
以上是内存分区的问题,下面是我理解的问题:
1.变量声明与定义:
声明只是告诉编译器有这么一个变量,而定义在声明的同时便进行内存分配。
//------1.cpp--------------

#include
using namespace std;
extern int n;//声明变量n,只告诉有这么一个名字
void function(int a){....};//定义形参a,并分配内存
int main(){
int i; //定义变量i,并分配内存
return 0;
}

其中,对于函数function,形参a的生存期在函数体内,在函数结束工作后,a便被释放,从栈中弹出。(a所占的内存和内存地址现在没有变量使用了),来看下面的代码;

#include
using namespace std;
int *p=NULL;
void fun(int a){ p=&a;}
int main(){
int n=10;
fun(n);
cout<return 0;
}

代码执行的结果是显示a的内存地址,然而在原来的我理解是a仍然占着这块内存区域,但是其实不是的,a早已被弹出栈,所以这块区域是空着的。之所以会这样,是因为指针p是全局的,p存放了a的地址,p的生存期没有结束,所以仍指向这块地址,所以会显示a的原来的内存地址。
­
由此,我就理解了传值返回和引用返回,为什么引用返回不能返回局部变量。第一,引用返回返回的是实际地址,即可用的。第二,如果返回局部变量,那么局部变量结束生存期被释放后,那块内存就空着了,没有变量用,所以不能操作........如果用传值返回返回局部变量却是可行的,因为传值返回在执行renturn 语句的时候要进行拷贝,把即将释放的局部变量的值拷贝到临时变量上。。
­
再来就是对于new和delete:
//-----------------------------2.cpp--------

#include
using namespace std;
int main(){
int *p=new int(4);//p存储在栈中,动态分配得到的内存存在堆中,从而通过指针指向一个无名字的堆来进行相关的操作.......
................
...........
delete p;//释放p, p还是指向原处,因为p没被释放,被释放的是无名字的堆内存区块!!
return 0;
}
所以,我就理解认为,其实在编写代码时用到的变量,只不过是给底层的内存区域提供一个名字,然后间接地操作内存的内容...................

分享到:
评论

相关推荐

    定义C/C++全局变量/常量几种方法的区别

    函数或变量在声明时,并没有给它实际的物理内存空间,它有时候可以保证你的程序编译通过, 但是当函数或变量定义的时候,它就在内存中有了实际的物理空间与区别。

    定义CC++全局变量常量几种方法的区别

    本文详细介绍了C++程序中的全局变量的声明、引用以及如何在头文件中定义全局变量

    c/c++ 学习总结 初学者必备

    4、 内存的分配方式的分配方式有几种? 答: (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。 (2)在栈上创建。在执行函数时,函数内局部变量的存储...

    谭浩强C语言教程Word版

    1 C语言概述 2&lt;br/&gt;1.1 C语言的发展过程 2&lt;br/&gt;1.2 当代最优秀的程序设计语言 2&lt;br/&gt;1.3 C语言版本 2&lt;br/&gt;1.4 C语言的特点 3&lt;br/&gt;1.5 面向对象的程序设计语言 3&lt;br/&gt;1.6 C和C++ 3&lt;br/&gt;1.7 简单的C程序介绍 4...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar

    8.8 局部变量和全局变量 112 8.8.1 局部变量 113 8.8.2 全局变量 119 8.9 变量的存储类别 120 78.9.1 动态存储方式与静态动态存储方式 120 8.9.2 auto变量 120 8.9.3 用static 声明局部变量 121 8.9.4 register 变量...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar )

    8.8 局部变量和全局变量 112 8.8.1 局部变量 113 8.8.2 全局变量 119 8.9 变量的存储类别 120 78.9.1 动态存储方式与静态动态存储方式 120 8.9.2 auto变量 120 8.9.3 用static 声明局部变量 121 8.9.4 register 变量...

    新手学习C++入门资料

    主体:(一)&lt;C++与C语言的区别&gt; 一、C++概述 (一) 发展历史 1980年,Bjarne Stroustrup博士开始着手创建一种模拟语言,能够具有面向对象的程序设计特色。在当时,面向对象编程还是一个比较新的理念,Stroustrup...

    谭浩强 入门c语言教程

    6.7 几种循环的比较 9 6.8 break和continue语句 9 6.8.1 break语句 9 6.8.2 continue 语句 10 6.9 程序举例 11 7 数组 1 7.1 一维数组的定义和引用 1 7.1.1 一维数组的定义方式 1 7.1.2 一维数组元素的引用 2 7.1.3 ...

    谭浩强c语言程序设计

    6.7 几种循环的比较 79 6.8 break和continue语句 79 6.8.1 break语句 79 6.8.2 continue 语句 80 6.9 程序举例 81 7 数组 7.1 一维数组的定义和引用 82 7.1.1 一维数组的定义方式 82 7.1.2 一维数组元素的引用 83 ...

    谭浩强 C语言程序设计 教程全书 Word版

    6.7 几种循环的比较 9 6.8 break和continue语句 9 6.8.1 break语句 9 6.8.2 continue 语句 10 6.9 程序举例 11 7 数组 1 7.1 一维数组的定义和引用 1 7.1.1 一维数组的定义方式 1 7.1.2 一维数组元素的引用 2 7.1.3 ...

    谭浩强版c语言程序设计

    6.7 几种循环的比较 79 6.8 break和continue语句 79 6.8.1 break语句 79 6.8.2 continue 语句 80 6.9 程序举例 81 7 数组 7.1 一维数组的定义和引用 82 7.1.1 一维数组的定义方式 82 7.1.2 一维数组元素的引用 83 ...

    《C语言程序设计》谭浩强

    6.7 几种循环的比较 79 6.8 break和continue语句 79 6.8.1 break语句 79 6.8.2 continue 语句 80 6.9 程序举例 81 7 数组 7.1 一维数组的定义和引用 82 7.1.1 一维数组的定义方式 82 7.1.2 一维数组元素的引用 83 ...

    最新名企标准通用C++面试题,

    15、内存的分配方式的分配方式有几种? 答: 1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。 2. 在栈上创建。在执行函数时,函数内局部变量的存储...

    c语言程序设计(第三版)

    6.7 几种循环的比较 9 6.8 break和continue语句 9 6.8.1 break语句 9 6.8.2 continue 语句 10 6.9 程序举例 11 6 7 数组 1 7.1 一维数组的定义和引用 1 7.1.1 一维数组的定义方式 1 7.1.2 一维数组元素的引用 2 ...

    Absolute C++中文版(原书第2版)-完美的C++教程,文档中还包含英文版

    3.3.3 全局常量与全局变量 83 3.3.4 语句块 84 3.3.5 嵌套作用域 85 3.3.6 for循环中声明的变量 85 第4章 参数与重载 92 4.1 参数 92 4.1.1 传值调用参数 92 4.1.2 引用调用参数初步 94 4.1.3 引用调用机制...

    谭浩强c语言word版

    6.7 几种循环的比较 79 6.8 break和continue语句 79 6.8.1 break语句 79 6.8.2 continue 语句 80 6.9 程序举例 81 7 数组 7.1 一维数组的定义和引用 82 7.1.1 一维数组的定义方式 82 7.1.2 一维数组元素的引用 83 ...

    谭浩强C程序设计第三版

    几种循环的比较 88 break和continue语句 88 break语句 88 continue 语句 89 程序举例 90 数组 94 一维数组的定义和引用 94 一维数组的定义方式 94 一维数组元素的引用 95 一维数组的初始化 96 一维数组程序举例 97 ...

    C程序设计 第四版 谭浩强 高清扫描版 带完整书签目录 加 学习辅导

    5.6 几种循环的比较 5.7 改变循环执行的状态 5.7.1 用break语句提前终止循环 5.7.2 用continue语句提前结束本次循环 5.7.3 break语句和continue语句的区别 5.8 循环程序举例 习题 第6章 利用数组处理批量数据 6.1 ...

    c语言(编写程序最佳参考资料)

    6.7 几种循环的比较... 9 6.8 break和continue语句... 9 6.8.1 break语句... 9 6.8.2 continue 语句... 10 6.9 程序举例... 11 7 数组... 1 7.1 一维数组的定义和引用... 1 7.1.1 一维数组的定义方式... 1 ...

Global site tag (gtag.js) - Google Analytics