V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hackpro
V2EX  ›  C

C++ static 关键字

  •  
  •   hackpro · 2019-06-04 16:55:24 +08:00 · 2986 次点击
    这是一个创建于 2029 天前的主题,其中的信息可能已经有所发展或是发生改变。

    标准里说对于 function 里面 static 变量只说了两点:

    1. 分配在静态存储区
    2. 第一次遇到的时候进行初始化,之后每次跳过。

    Variables declared at block scope with the specifier static have static storage duration but are initialized the first time control passes through their declaration (unless their initialization is zero- or constant-initialization, which can be performed before the block is first entered). On all further calls, the declaration is skipped.

    https://en.cppreference.com/w/cpp/language/storage_duration

    比如

    int f()
    {
    	static int a = 0;
    }
    
    int main()
    {
    	f();
    	std::cout << "---------------" << std::endl;
    	f();
    
    	return 0;
    }
    
    1. 程序起来,静态存储区创建 int a; 此时并未运行到 f(),这时静态存储区的值 a 是否 undefined?还是说类似全局变量初始化,数值类型给 0 值,字符串给空?
    2. 第一次进入 f()运行到 static int a = 0 时,静态存储区的 a 被初始化为 0 ?之后每次进入 f()这个语句直接被忽略掉?编译器是如何实现的?类似于一个 loop ?初始值 cnt=1,用完 cnt--,下次 check 值是否为 0 进行跳转?
    11 条回复    2019-06-05 01:56:06 +08:00
    zycpp
        1
    zycpp  
       2019-06-04 17:31:54 +08:00 via iPhone
    #include <iostream>

    class A{
    public:
    A(){printf("create\n");}
    ~A(){printf("destroy\n");}
    };

    void f(){
    static A a;
    }

    int main(){
    printf("before call f\n");
    f();
    printf("after call f\n");
    f();
    printf("the end\n");
    return 0;
    }
    wutiantong
        2
    wutiantong  
       2019-06-04 17:45:04 +08:00 via iPhone
    问题 1,如果你没运行到 f,a 是不存在的(未分配内存),更不必讨论初始化。这点你在引文里其实已经提到了。

    编译器如何实现的我也不清楚,但一定不是你说的这样
    Akiyu
        3
    Akiyu  
       2019-06-04 17:57:49 +08:00
    你分析汇编就知道了
    static 具有线程安全性, 因为访问的时候会加锁
    我记得我写过笔记, 我找找啊 = =
    yianing
        4
    yianing  
       2019-06-04 18:10:18 +08:00 via Android
    static 变量在程序装载到内存时就会被初始化,有初值的赋初值,没有的是 0,程序员的自我修养书上有
    Akiyu
        5
    Akiyu  
       2019-06-04 18:19:58 +08:00
    找不到了, 直接分析一下吧 = =

    原始代码 :
    void func(int a) {
    static int i = a;
    cout << i << endl;
    i++;
    }

    汇编代码:
    -- 这是调用的:
    movl▹ $100, %edi
    call▹ _Z4funci
    -- 这是 func 函数的:
    _Z4funci:
    .LFB3222:
    ▹ pushq▹ %rbp
    ▹ movq▹ %rsp, %rbp
    ▹ subq▹ $16, %rsp
    ▹ movl▹ %edi, -4(%rbp) // 参数入栈
    ▹ movl▹ $_ZGVZ4funciE1i, %eax
    ▹ movzbl▹ (%rax), %eax // 这里就是某个标志位, 标识了静态变量是否被初始化了, 当翻译成机器码时, 这是一个绝对地址
    ▹ testb▹ %al, %al // 检测某个位
    ▹ jne▹.L2
    ▹ movl▹ $_ZGVZ4funciE1i, %edi
    ▹ call▹ __cxa_guard_acquire // 关键字 guard_acquire, 获取锁
    ▹ testl▹ %eax, %eax
    ▹ setne▹ %al
    ▹ testb▹ %al, %al
    ▹ je▹ .L2
    ▹ movl▹ -4(%rbp), %eax // 参数放入寄存器 eax
    ▹ movl▹ %eax, _ZZ4funciE1i(%rip) // 经过前面的检测后, 这里才会设置值
    ▹ movl▹ $_ZGVZ4funciE1i, %edi
    ▹ call▹ __cxa_guard_release // 关键字 guard_release, 释放锁
    .L2:
    ▹ movl▹ _ZZ4funciE1i(%rip), %eax // 这里是直接使用的, 前面的 jne 会跳转到这里
    ▹ movl▹ %eax, %esi
    ▹ movl▹ $_ZSt4cout, %edi
    ▹ call▹ _ZNSolsEi
    ▹ movl▹ $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esi
    ▹ movq▹ %rax, %rdi
    ▹ call▹ _ZNSolsEPFRSoS_E
    ▹ movl▹ _ZZ4funciE1i(%rip), %eax
    ▹ addl▹ $1, %eax
    ▹ movl▹ %eax, _ZZ4funciE1i(%rip)
    ▹ leave
    ▹ ret

    简单来说, 内存中有一个绝对地址(由编译器提供并维护)的某个位, 它标志了静态变量是否被初始化
    然后如果未初始化, 先会获得锁, 初始化, 设置完后再释放锁
    Akiyu
        6
    Akiyu  
       2019-06-04 18:20:54 +08:00
    当然, 这视编译器而异
    有些的实现是直接初始化了
    sky2017
        7
    sky2017  
       2019-06-04 19:11:33 +08:00
    局部 static 变量是第一次执行到的时候才初始化,并且存在线程安全问题
    zmj1316
        8
    zmj1316  
       2019-06-04 19:20:52 +08:00 via Android
    @Akiyu 好像是 c++ 11 开始才是线程安全的
    Akiyu
        9
    Akiyu  
       2019-06-04 19:33:44 +08:00
    @zmj1316
    这个我就不太清楚了, 我直接学的 C++ 11
    zzjas98
        10
    zzjas98  
       2019-06-05 01:37:04 +08:00
    Q1: static 的东西会被存到 bss 去,看下这里可能有帮助 https://en.wikipedia.org/wiki/.bss

    TL;DR
    “ On some platforms, some or all of the bss section is initialized to zeroes. Unix-like systems and Windows initialize the bss section to zero, allowing C and C++ statically-allocated variables initialized to values represented with all bits zero to be put in the bss segment.”

    Q2:
    static int a = 0 这一句第一次执行之后,以后再遇到会被跳过。具体怎么实现不知道,可能因编译器而异吧

    借用 5 楼的例子多写了一些:
    https://imgur.com/RNdB7vL
    https://imgur.com/3ZRnxxN
    zzjas98
        11
    zzjas98  
       2019-06-05 01:56:06 +08:00
    啊不对才注意到到楼主的例子是 static int a = 0,我 Q1 说的是针对“ static int a;”的。还真没找到 function scoped,initialized,static variable 被 initialize 之前的值是什么。。。但是它肯定会在第一次 function invocation 时候被 intialize,以后执行会直接跳过那句。
    这个链接或许有帮助:
    https://web.archive.org/web/20100328062506/http://www.acm.org/crossroads/xrds2-4/ovp.html

    另外,这个或者 1 楼的回复可以测试那个 declaration 被执行了几次

    #include <iostream>
    using namespace std;

    void func(int a) {
    static int i = (printf("declaration\n")) ? a:a;
    cout << i << endl;
    i++;
    }

    int main() {
    func(5);
    func(10);
    }
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2765 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 14:59 · PVG 22:59 · LAX 06:59 · JFK 09:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.