[cpp deep dive] 一些奇怪的关键字_不那么奇怪的static

个人理解,如果有误请及时指出,我将非常感谢!
还是从一个题目开始吧~

  • 关于C++/Java类中的static成员和对象成员的说法正确的是:
    A:static成员变量在对象构造时候生成
    B: static成员函数在对象成员函数中无法调用
    C: 虚成员函数不可能是static成员函数
    D: static成员函数不能访问static成员变量
    参考答案:C

step 0. static变量的存放位置 - 静态存储区

我整理如下(已更新)


静态/动态存储区.png
  • 这里的静态/动态指的是:
    • 静态:在程序运行过程中,1. 空间大小不变,这些空间在程序结束后由系统回收. 2.存在这些区域的变量生命周期贯穿整个程序(进程).
    • 动态:程序运行过程中,1. 大小可变,有可能会动态地进行申请/释放,2. 这里的变量声明周期不是贯穿整个程序,而是可能由程序员控制的。
    • 相对的,需要区分静/动态与读写/只读属性.
  • 附:


    知乎上的一个答案(<a href=https://www.zhihu.com/question/28346079>链接</a>)
  • 附2:验证一下:
  1 #include "common.h"                                                                                                                                                                        
  2 int g_uninitial;                                                                
  3 int g_uni[1024];                                                                
  4                                                                                 
  5 int g_initial_d = 1000;                                                         
  6                                                                                 
  7 int main()                                                                      
  8 {                                                                               
  9     static int local_static_var = 100;                                          
 10     static int local_static_un_var;                                             
 11     int stack_var = 100;                                                        
 12     const int ss = 100;                                                         
 13                                                                                 
 14     printf("constant var          -  %p\n", &ss);                               
 15     printf("local var             -  %p\n", &stack_var);                        
 16                                                                                 
 17     printf("lo_sta_uninit(.bss)   -  %p (value %d)\n", &local_static_un_var, local_static_un_var);
 18     printf("global_uninited(.bss) -  %p (value %d)\n", &g_uninitial, g_uninitial);
 19                                                                                 
 20                                                                                 
 21     printf("global_inited(.data)  -  %p\n", &g_initial_d);                      
 22     printf("local_static(.data)   -  %p\n", &local_static_var);                 
 23     printf("constant              -  %p\n", "abcd");                            
 24     printf("MAIN's addr(.text)    -  %p\n", main);                              
 25                                                                                 
 26     return 0;                                                                   
 27 }

结果输出.

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
constant var          -  0x7ffc9760ee6c  <--------所以const变量本身还是位于堆栈内部,const仅仅是个只读标记而已.
local var             -  0x7ffc9760ee68
lo_sta_uninit(.bss)   -  0x6020a4 (value 0) 
global_uninited(.bss) -  0x601080 (value 0)  <------至于谁高谁低就需要了解装入/分配机制了
global_inited(.data)  -  0x601058
local_static(.data)   -  0x60105c
constant              -  0x400982 <-------------字符串常量,位于main程序所在位置上面一点
MAIN's addr(.text)    -  0x40070d
  • 总结一下要点:
  • 字符串常量位于.text段.
  • 全局初始化变量(包括static)位于.data段.
  • 全局未初始化变量位于.bss段,且以0填充(linux).
  • 关于 malloc和calloc的验证:
code(代码贴图差评!@!!)
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
0 0 0 0 0 0 0 0 16 66 80 11 -128 52 7 -16 32 68 -112 0 -126 84 11 113 48 64 -48 27 -125 116 15 -16 64 72 17 35 -124 -108 0 114 80 74 81 43 -128 -76 23 -14 96 76 -112 0 -122 -44 27 115 112 64 -47 59 -121 -12 31 -16 -128 80 18 67 -120 20 0 116 -112 82 82 75 -128 53 39 -12 -96 84 -112 0 -118 85 43 117 -80 64 -46 91 -117 117 47 -16 -64 88 19 99 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

step 1. C中的static

  • c中的static关键字有个重要作用,是使该变量(全局static变量)仅本文件可见.
  • c中局部static变量,每次访问时都是对同一个变量做操作,并不会因为函数退出而销毁.
#include "common.h"

void functest(){
    static int x = 0;
    x++;
    cout<<x<<endl;
}

int main()
{
    functest();
    functest();
    return 0;
}

输出

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
1
2

step 2. 面向对象中的static. 如何使用.

  • 类的static成员,类内只是声明,需要在类外定义,否则链接阶段出错:未定义的标识符(ld2: undefined reference to...)。(phase 1)
    • 类的static成员,类外定义的数据类型需要跟声明时是相同的.定义的语法是
      <Type> <class-name>::<member_name>[ = initial_value];
  • 类的static成员不占该类大小的空间_因为static成员是位于.data段的。(phase 1)
  • 附3:(验证ok)
  1 #include "common.h"                                                                                                                                                                        
  2 int g_uninitial;                                                                
  3 int g_uni[1024];                                                                
  4                                                                                 
  5 int g_initial_d = 1000;                                                         
  6 class base{                                                                     
  7     public:                                                                     
  8         static int ss1;                                                         
  9         static double ss2;                                                      
 10         static void func();                                                     
 11 };                                                                              
 12 int base::ss1;                                                                  
 13 double base::ss2 = 100;                                                         
 14                                                                                 
 15 int main()                                                                      
 16 {                                                                               
 17     static int local_static_var = 100;                                          
 18     static int local_static_un_var;                                             
 19     int stack_var = 100;                                                        
 20     const int ss = 100;                                                         
 21                                                                                 
 22     printf("constant var          -  %p\n", &ss);                               
 23     printf("local var             -  %p\n", &stack_var);                        
 24                                                                                 
 25     printf("global_uninited(.bss) -  %p (value %d)\n", &g_uninitial, g_uninitial);
 26     printf("lo_sta_uninit(.bss)   -  %p (value %d)\n", &local_static_un_var, local_static_un_var);
 27     printf("base::ss1             -  %p (value %d)\n", &base::ss1, base::ss1);  
 28                                                                                 
 29     printf("global_inited(.data)  -  %p\n", &g_initial_d);                      
 30     printf("local_static(.data)   -  %p\n", &local_static_var);                 
 31     printf("base::ss2             -  %p\n", &base::ss2);                        
 32                                                                                 
 33     printf("constant              -  %p\n", "abcd");
 34     printf("MAIN's addr(.text)    -  %p\n", main);                              
 35                                                                                 
 36     printf("sizeof(base)  - %ld\n", sizeof(base));                              
 37     return 0;                                                                   
 38 }   

输出

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
constant var          -  0x7fff562b87dc
local var             -  0x7fff562b87d8
global_uninited(.bss) -  0x6010a0 (value 0)
lo_sta_uninit(.bss)   -  0x6020c8 (value 0)
base::ss1             -  0x6020c0 (value 0)
global_inited(.data)  -  0x601058
local_static(.data)   -  0x601068
base::ss2             -  0x601060
constant              -  0x400a17
MAIN's addr(.text)    -  0x40070d
sizeof(base)  - 1

  • 类的非static成员函数,需要通过具体对象调用,不能以类似类名::函数名来调用。 (1)

    • 否则,错误信息将类似error: cannot call member function ‘int base::out()’ without object.
  • 类的非static成员函数可以访问static成员和static成员函数(ok)(2)

  • 类的static成员函数,static成员函数不能访问非static成员。也就是说static成员函数只能访问static成员。(3)

    • 否则,错误信息将类似invalid use of member ‘base::kk’ in static member function
  • 类的static成员函数在声明时语法: static <ret-type> <func-name>(list_of_parameters...);同时,在定义该成员函数时,不需要再写一遍static了。

  • 类的static成员函数不能是virtual的,因此就不具备多态。(后半句存在争议,主要是对“多态”这个词的定义)(4)

    • 类的static成员函数可以是具有不同参数列表的重载函数(overload),但不能是virtual。这里需要区分重载并不是多态.
    • 重载是静态多态(编译期绑定,静态绑定),区别于我们常说的多态(需要在继承关系中发生的即virtual,动态绑定,也就是override,运行时决定)。

附4:

(扩展上面的代码)
#include "common.h"
int g_uninitial;
int g_uni[1024];

int g_initial_d = 1000;
class base{
    public:
        static int ss1;
        static double ss2;
        //virtual static void func2(double k); //error: member ‘func’ cannot be declared both virtual and static.........(4)
        static void func(double k); 
        static void func(int k);
        int out(){//成员函数也不占类的大小.........(2)
            ____("out----");
            ss1++;
            cout<<ss1<<endl;
            func(ss1);          
        }
    private:
        int kk;
};
int base::ss1;
double base::ss2 = 100.2;
void base::func(double k){
    //cout<<kk<<endl; error: invalid use of member ‘base::kk’ in static member function (3)
    ____("double func---");
    cout<<k<<endl;
    cout<<ss2<<endl;
}
void base::func(int kk){
    ____("int func---");
    cout<<kk<<endl;//局部同名变量(参数)
    cout<<ss1<<endl;
}



int main()
{
    static int local_static_var = 100;
    static int local_static_un_var;
    int stack_var = 100;
    const int ss = 100;
    
    printf("constant var          -  %p\n", &ss);
    printf("local var             -  %p\n", &stack_var);
    
    printf("global_uninited(.bss) -  %p (value %d)\n", &g_uninitial, g_uninitial);
    printf("lo_sta_uninit(.bss)   -  %p (value %d)\n", &local_static_un_var, local_static_un_var);
    printf("base::ss1             -  %p (value %d)\n", &base::ss1, base::ss1);
    
    printf("global_inited(.data)  -  %p\n", &g_initial_d);
    printf("local_static(.data)   -  %p\n", &local_static_var);
    printf("base::ss2             -  %p\n", &base::ss2);
    
    printf("constant              -  %p\n", "abcd");
    printf("MAIN's addr(.text)    -  %p\n", main);

    printf("sizeof(base)  - %ld\n", sizeof(base));
    ____("----------");
    
    
    base::func(1);
    base::func((double)2);  //overload not override
    //base::out();  error: cannot call member function ‘int base::out()’ without object base::out();............(1)
    
    ____("-----------");
    base bb1;
    bb1.out();
    
    base bb2;
    bb2.out();
    
    return 0;
}

输出

constant var          -  0x7ffdb0b98e4c
local var             -  0x7ffdb0b98e48
global_uninited(.bss) -  0x6021c0 (value 0)
lo_sta_uninit(.bss)   -  0x6031e8 (value 0)
base::ss1             -  0x6031e0 (value 0)
global_inited(.data)  -  0x602080
local_static(.data)   -  0x602090
base::ss2             -  0x602088
constant              -  0x400d77
MAIN's addr(.text)    -  0x4009b4
sizeof(base)  - 4
----------
int func---
1
0
double func---
2
100.2
-----------
out----
1
int func---
1
1
out----
2
int func---
2
2

step 3. 如何应用到实际工程中

  • c++面向对象可以用static来实现单例模式.很多情况都需要单例,比如某个工程需要一个日志模块。
    你肯定不能这里一个日志那里一个日志,而是整个工程就一个日志,各个模块把日志信息都输入到同一个日志对象中。

Ref:

  • <a href = http://blog.csdn.net/crayondeng/article/details/24853471>单例模式的探究</a>

归纳一下(黑字是我的补充):
上面的链接中给的一些示例:

  1. 单例模式 != 全静态函数(失去了多态性)
  2. 单例模式的饿汉模式(运行时立刻执行初始化,m_*是个本类类型,本类静态成员)
  • 初始化顺序没有保证
  1. 单例模式的懒汉模式(改进版)(当引用对象时才进行初始化,m_*是个本类类型指针,getInstance()函数的局部静态变量)
  • 线程不安全 可以通过加锁(双检锁)来解决
  • 有内存泄漏的危险 可以增加一个destruct函数来解决
  1. 单例模式的代理模式(通过一个静态嵌套类成员来实现)

step 3. 按上面的建议实现一个单例模式 并在多线程环境中测试

由于工程经验尚不是很多,4我就暂时不考虑,先来个懒汉模式 + 双检锁(DCL)吧.
值得一提的是第二个Ref材料中说明了一个问题,就是static静态成员指针可能被编译器优化(主要是执行顺序的问题),导致一些情况下运行不正常,需要增加volatile关键字.(我先不管那么多)

关于执行顺序主要有2种办法:(目前所知)

1.内存栅栏

  • levelDB中有2种实现
    • e
    • d

2.volatile关键字(需要把volatile搞得遍地都是..)

遗留的问题有:

  • 嵌套代理类
  • volatile关键字
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容