首页
社区
课程
招聘
[原创]C++ static关键字引发的思考
发表于: 2023-12-9 20:57 11272

[原创]C++ static关键字引发的思考

2023-12-9 20:57
11272

基本用法

在面向对象中的用法

在类中,可以使用static关键字修饰成员函数和变量,被修饰后的函数或变量被称为静态成员函数或变量。它们属于整个类,不属于某一个对象,这意味着无需创建对象即可访问静态成员函数或变量。最常见的一个用法就是单例模式(整个类仅可只有一个对象),例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Singleton
{
public:
    static Singleton& instance() // 静态方法
    {
        static Singleton inst;  // 静态对象在instance中声明
        return inst;
    }
    int& get() { return value_; }
private:
    Singleton() : value_(0) { std::cout << "Singleton::Singleton()" << std::endl; }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    ~Singleton() { std::cout << "Singleton::~Singleton()" << std::endl; }
private:
    int value_;                 // 非静态成员变量
};
 
int value = Singleton::instance().get() // Singleton::instance()返回静态对象inst

在面向过程中的用法

此处的static主要用于限制修饰对象的作用域。

  • 静态函数,该函数仅可在当前文件中使用。
  • 静态变量,该变量仅可在当前文件中使用,而全局变量可导出给别的文件使用,虽然两者都是将变量存储在.data区域,且在程序整个声明周期都有效。
  • 静态局部变量,该变量只能被初始化一次,仅可在当前函数中使用。

新的问题

面向过程中的静态局部变量"只能被初始化一次"是如何实现的?
考虑到静态变量有可能被多次初始化且使用变量初始化,使用的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include
#include
#include          // std::chrono::seconds
 
using namespace std;
 
void func_a();
void func_b(int a, int b);
 
int main()
{
    func_a();
    thread t1(func_b, 1, 2);
    thread t2(func_b, 3, 4);
    thread t3(func_b, 5, 6);
    thread t4(func_b, 7, 8);
 
    std::this_thread::sleep_for(std::chrono::seconds(1));
    getchar();
    t1.join();t2.join();t3.join();t4.join();
    return 0;
}
 
void func_a()
{
    static int func_a_value = 0x1234;
    cout << "func_a_value => " << func_a_value << ", current function = > " << __FUNCTION__ << endl;
}
 
void func_b(int a, int b)
{
    static int res = a + b;
    cout << "res => " << res << ", current thread id => " << std::this_thread::get_id() << endl;
}

Windows下的MSVC编译器的实现

静态局部变量func_a_value

func_a函数中是以常量初始化静态局部变量func_a_value,因此编译器直接将0x1234写入.data对应的位置,这与单(多)线程无关,与单(多)次初始化无关。
00
若函数以变量形式初始化静态局部变量,则实现初始化一次的原理见静态局部变量res

静态局部变量res

存在多个线程初始化res的情况,因此需要进行线程同步。IDA反汇编func_b函数之后的结果如下:
01
对应的C++代码如下:

1
2
3
4
5
6
7
8
9
if(pOnce > *(int *)(NtCurrentTeb()->ThreadLocalStoragePointer[0] + 0x104)  // tls中存储的全局 局部静态变量初始化计数器, 初始值为INT_MIN(0x80000000)
{
    _Init_thread_header(&pOnce)
    if ( pOnce == -1 )
    {
      res = b + a;    // 初始化代码
      _Init_thread_footer(&pOnce);
    }
}

_Init_thread_header的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 代码所在的文件为thread_safe_statics.cpp
extern "C" void __cdecl _Init_thread_header(int* const pOnce) noexcept
{
    _Init_thread_lock();                // 进入临界区
 
    if (*pOnce == uninitialized)        // uninitialized = 0
    {
        *pOnce = being_initialized;     // being_initialized = -1
    }
    else
    {
        while (*pOnce == being_initialized)
        {
            // Timeout can be replaced with an infinite wait when XP support is
            // removed or the XP-based condition variable is sophisticated enough
            // to guarantee all waiting threads will be woken when the variable is
            // signalled.
            _Init_thread_wait(xp_timeout);
 
            if (*pOnce == uninitialized)
            {
                *pOnce = being_initialized;
                _Init_thread_unlock();
                return;
            }
        }
        _Init_thread_epoch = _Init_global_epoch; // _Init_global_epoch = INT_MIN
    }
 
    _Init_thread_unlock();              // 离开临界区
}

[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

最后于 2023-12-9 22:04 被baolongshou编辑 ,原因:
上传的附件:
收藏
免费 7
支持
分享
最新回复 (2)
雪    币: 4955
活跃值: (4048)
能力值: ( LV13,RANK:270 )
在线值:
发帖
回帖
粉丝
2
mac os 平台留给观众自己分析一下了
2023-12-9 21:32
0
雪    币: 4033
活跃值: (31446)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-12-9 23:33
1
游客
登录 | 注册 方可回帖
返回