static变量 static函数

首先要明白c语言的存储空间

转自:http://www.52rd.com/blog/Detail_RD.Blog_imjacob_5297.html

一、c程序存储空间布局

C程序一直由下列部分组成:

      1)正文段——CPU执行的机器指令部分;一个程序只有一个副本;只读,防止程序由于意外事故而修改自身指令;
      2)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
      3)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0。
      4)栈——增长方向:
自顶向下增长;自动变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。
      5)堆——动态存储分。自下而上增长

|-----------|
|                 |
|-----------|
|    栈         |  高地址位
|-----------|
|    |            | 
|   |/           |
|                 |
|                 |
|   /|           |
|    |            | 
|-----------|
|    堆         |
|-----------|
| 未初始化  |
|-----------|
|   初始化  |
|-----------|
|  正文段   |低地址位
|-----------|


从而c语言变量有三种生命空间:程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。

如果在utility.h中定义了一个全局变量 如 int n = 20;

则这个头文件只能被include一次,否则会出错;例如在utility.cpp 中include "utility.h",然后又在main.cpp中include "utility.h";include的意思就是把头文件的内容全部输出到include之处,这样的话,就相当于在utility.cpp中定义一个全局变量n,在main.cpp中又定义一个全局变量n,正如上文提及到的,整个工程的全局变量都放在一个地方,这样就会出现重复定义。

如何在utility.h中定义一个全局变量,从而在工程的任何一个源文件,只要include一下这个头文件,就可以直接用这个全局变量呢?有三种方法:http://hi.baidu.com/maydaygmail/blog/item/52fc1c0b6413d13e6159f3d7.html

1. (推荐)在utility.h中声明为extern int n(意思是指这只是一个声明,定义在外部);然后,只要在任意一个.cpp 文件中再定义一次,只能定义一次,int n =10;在工程的任意地方就可以使用全局变量n,还可以修改n的值

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全局变量时,都把它放在原文件中而不是头文件,这样就不会给其他模块造成不必要的信息污染,同样记住这个原则吧!

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".

也可以直接在头文件中定义const char*g_str="123456"

工程任何地方都可以使用g_str,但是不能修改值


在举一个例子:

//utiltiy.h

.......

//utility.cpp

#include "utility.h"
       static int m = 6;

//main.cpp

#include"utility.h"

int main()

{

cout<<m;

}

首先,虽然在main.cpp中include"utility.h",但是头文件里没有定义m所以,必须extern int m,表明这个m是外部的。如果在int main()之前声明了extern int m;依然有错,这是因为m因为有static现在,所以m只能用于utility.cpp中;

所以要改为:

//utility.cpp

#include "utility.h"
   int m = 6;

//main.cpp

#include"utility.h"

extern int m;

int main()

{

cout<<m;

}

这样就对了。

如果这样写,对不对呢:

//utiltiy.h

 static int m = 6;

//utility.cpp

#include "utility.h"
      

//main.cpp

#include"utility.h"

int main()

{

cout<<m;

}

这样是对的,虽然在utility.cpp和main.cpp中都有m的定义,但是此时m被static现在,就是说这两个m只能在这两个cpp文件中有效,所以不会冲突;

如果把static去掉,则就有两个全局变量m,所以出错


4. 如果在.h文件中定义的全局变量,用于只读的,还可以在头文件中这样定义:

    #define G_VAL  5

  或者

  enum {G_VAL = 5};


  在其他文件中,只要include这个头文件,就可以直接读取G_VAL


下面讲一下static函数


主要参考:http://hi.baidu.com/pope123/blog/item/344407d5512953d450da4b6c.html

在平常的 C/C++ 开发中,几乎所有的人都已经习惯了把类和函数分离放置,一个 .h 的头文件里放声明,对应的 .c 或者 .cpp 中放实现。从开始接触,到熟练使用,几乎已经形成了下意识的流程。在 Symbian OS 下编程,则更是如此,再小的类也会分成两个文件,几乎没有人想去改变。

尽管这样的做法无可厚非,而且在不少情况下是相对合理甚至必须的,但我还是要给大家介绍一下把实现全部放置到头文件中的方式,给出可供大家使用的另一个选择。同时针对这一做法,也顺便说一下其优缺点以及需要注意的情况。

我是一个很喜欢简洁的人,多年以来甚至养成了这样的癖好,如果一个功能是能够用一条语句实现的,那就不要用两条语句。在我看来,如果给别人提供一份可以复用的代码的话,最优雅的状态莫过于仅仅提供一个头文件就全部搞定。

之所以不太喜欢引入源文件,最重要的原因是源文件往往会带来工程文件的变化;而且,在使用过程中也会增加一些额外的操作,例如,在一个组织良好的工程里,头文件和源文件很有可能是位于不同的目录,这样就会多带来一次文件复制操作。

2 、正文

2.1 顾虑

我遇到有不少人不使用头文件来包含实现,往往是出于以下几种顾虑:

1、              暴露了实现细节

2、              头文件被包含到不同的源文件中,会导致链接冲突

3、              头文件被包含到不同的源文件中,会导致有多份实现被编译出来,增大可执行体的体积

如果有顾虑 1 ,那很显然应该在第一时间抛弃完全在头文件中实现的念头。不过我遇到的情形里,通常后两种顾虑占据了绝对的比例。而这种顾虑,通常是由于对 C/C++ 没有足够的了解导致的。

有顾虑 2 的,经常会是一些有 C 语言开发经验的程序员。他们所担心的也往往是出现的全局函数的情况。例如有以下头文件 c_function.h (清晰起见,防卫宏之类的代码没有列出):

int integer_add(const int a, const int b)

{

         return a + b;

}

如果在同一工程中,有 a.c (或者是 .cpp )和 b.c 两个(或两个以上)源文件包含了此头文件,则在链接时期就会发生冲突,因为在两个源文件编译得到的目标文件中都有一份 integer_add 的函数实现,导致链接器不知道对于调用了此函数的调用者,应该使用哪一个副本。(此时有两个全局函数int integer_add(const int a, const int b)

2.2 着手

解决的办法有两个,各自为两个关键字,一个是 inline ,另一个是 static 。使用这两个关键字的任意一个来修饰 integer_add 函数,都会消除上述的冲突问题,然而本质却大不相同。

如果使用 inline ,则意味着编译器会在调用此函数的地方把函数的目标代码直接插入,而不是放置一个真正的函数调用,实际作用就是这个函数事实上已经不再存在,而是像宏一样被就地展开了。使用 inline 的副作用,首先在于毋庸置疑地,代码的体积变大了;其次则是,这个关键字严格算起来并不是 C 语言的关键字,使用它多少会带来一些移植性方面的风险,尽管主流的 C 语言编译器都可以支持 inline 。对于 GCC , inline 功能关键字就是 inline 本身,而对于微软的编译器,应该是 __inline (注意有两个前导下划线)。

而且,根据惯例, inline 通常都是对编译器的某种暗示而非强制要求,编译器有权力在你不知情的情况下把它实现为非 inline 的状态(可能的原因有,函数太大或者复杂度过高)。这样的后果是什么,不好意思,我没有测试过。

如果是使用 static ,那么至少结果是可预料的。所有包含此头文件的源文件中都会存在此函数的一份副本。虽然代码也有一定程度的膨胀,但好就好在互相不冲突,因为 static 关键字保证了该函数的可见度为单个源文件之内



static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?

全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。

static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件

static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;

static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;

static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝


总结,static限定了全局变量或者函数的作用域,使其只能在本编译单元


static变量只被定义一次,如下题:

unsigned int sum_int( unsigned int base )
{
 unsigned int index;
 static unsigned int sum = 0; // 注意,是static类型的。 
 for (index = 1; index <= base; index++)
 {
  sum += index;
 }
 return sum;
}

  答案与分析:

  所谓的函数是可重入的(也可以说是可预测的),即:只要输入数据相同就应产生相同的输出。
  这个函数之所以是不可预测的,就是因为函数中使用了static变量,因为static变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量,这种函数中的static变量,使用原则是,能不用尽量不用。
  将上面的函数修改为可重入的函数很简单,只要将声明sum变量中的static关键字去掉,变量sum即变为一个auto 类型的变量,函数即变为一个可重入的函数。
  当然,有些时候,在函数中是必须要使用static变量的,比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值