3.C++存储类(static,extern,thread_local)

C++存储类

存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。(auto和register省略)

  • static
  • extern
  • mutable
  • thread_local(c++11版本开始有)

1)static存储类
第一篇里面有,觉得重要又搬过来。
1.全局静态变量:
定义:在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.
内存位置:静态存储区
初始化:未经初始化的全局静态变量会被自动初始化为0(而自动对象的值是任意的)
作用域:从定义开始到文件结尾,别的文件不可见。

2.局部静态变量
定义:在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。
内存位置:静态存储区
初始化:未经初始化的局部静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问直到该函数再次被调用,并且值不变;(static定义的静态局部变量分配在数据段上,普通的局部变量分配在上,会因为函数栈帧的释放而被释放掉)

3.静态函数
在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用
warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰

4.类的静态成员
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用

5.类的静态函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。

在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);

总结:
1.对于函数定义和代码块之外的变量声明,static修改标识符的链接属性由默认的external变为internal,作用域和存储类型不改变,这些符号只能在声明它们的源文件中访问
2.对于代码块内部的变量声明,static修改标识符的存储类型,由自动变量改为静态变量,作用域和链接属性不变。这种变量在程序执行之前就创建,在程序执行的整个周期都存在。(static定义的静态局部变量分配在数据段上,普通的局部变量分配在上,会因为函数栈帧的释放而被释放掉)
3.对于被static修饰的类成员变量和成员函数,它们是属于类的,而不是某个对象所有对象共享一个静态成员。静态成员通过<类名>::<静态成员>来使用。加了static关键字,则此变量/函数就没有了this指针了,必须通过类名才能访问。

2)extern存储类
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
即可以在1.cpp中声明一个全局函数/全局变量,在2.cpp中即可不声明直接定义。
如:
1.cpp

#include <iostream>
 
int count ;
extern void write_extern();
 
int main()
{
   count = 5;
   write_extern();
}

2.cpp

#include <iostream>
 
extern int count;
 
void write_extern(void)
{
   std::cout << "Count is " << count << std::endl;
}

现在 ,编译这两个文件,执行得到:

Count is 5

3)mutable存储类
mutable 说明符仅适用于类的对象,更到类时回来补。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。

4)thread_local 存储类
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。

thread_local 说明符可以与 static 或 extern 合并。这将影响变量的链接属性。

那么,哪些变量可以被声明为thread_local?以下3类都是ok的
1.命名空间下的全局变量
2.类的static成员变量
3.本地变量
即可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
以下演示了可以被声明为 thread_local 的变量:

thread_local int x;  // 命名空间下的全局变量
class X
{
    static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s;  // X::s 是需要定义的
 
void foo()
{
    thread_local std::vector<int> v;  // 本地变量
}

既然每个线程都拥有一份独立的thread_local变量,那么就有2个问题需要考虑:

1.各线程的thread_local变量是如何初始化
2.各线程的thread_local变量在初始化之后拥有怎样的生命周期,特别是被声明为thread_local的本地变量(local variables)
看下例:

#include <thread>

thread_local int g_n = 1; //全局变量

void f()//使g_n自加,打印线程id和g_n
{
    g_n++; 
    printf("id=%d, n=%d\n", std::this_thread::get_id(),g_n);
}

void foo()//本地变量,定义i为0,打印id和i后i自加
{
    thread_local int i=0;
    printf("id=%d, n=%d\n", std::this_thread::get_id(), i);
    i++;
}

void f2()//运行两次foo函数
{
    foo();
    foo();
}

int main()
{
    g_n++; //主线程g_n=2
    f();  //主线程g_n=3  
    std::thread t1(f);//线程t1进入f之前g_n为1,进入之后输出2
    std::thread t2(f);//线程t2进入f之前g_n为1,进入之后输出2
    
    t1.join();
    t2.join();


    f2();//运行两次foo函数,第一次为主线程的id8004,n为i=0,随后i自加为1.注意第二次foo不需要初始化i
    std::thread t4(f2);
    std::thread t5(f2);

    t4.join();
    t5.join();
    return 0;
}

输出:

id=8004, n=3//线程的id8004,主线程g_n=3 
id=8008, n=2//线程t1 id8008 进入f之前g_n为1,进入之后输出2
id=8012, n=2//线程t2 id8012 进入f之前g_n为1,进入之后输出2
id=8004, n=0//主线程开始运行两次foo函数,第一次为主线程的id8004,n第一次初始化为i=0,随后i自加为1
id=8004, n=1//主线程第二次进入foo不需要初始化i,直接输出上次i=1
id=8016, n=0
id=8016, n=1
id=8020, n=0
id=8020, n=1

1.输出的前3行打印能帮助解答thread_local变量是如何初始化的,可以看到每个线程都会进行一次初始化。例子中的g_n在主线程中最早被初始化为1,随后被修改为2和3。
虽然t1和t2线程启动的时候主线程中的变量值已经被更新为3,但主线程修改操作并不影响g_n在线程t1和t2中的初始值(值为1),,所以主线程、t1、t2打印结果分别为3,2,2。
2.后6行打印说明了一个事实,声明为thread_local的本地变量在线程中是持续存在的,不同于普通临时变量的生命周期,它具有static变量一样的初始化特征和生命周期,虽然它并没有被声明为static。例子中foo函数中的thread_local变量 i 在每个线程第一次执行到的时候初始化,在每个线程各自累加,在线程结束时释放
总结要注意的就是:
某线程生命周期内被thread_local修饰的本地变量只初始化一次

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值