今天在工程中使用jsoncpp时,发现一个问题。
现在把问题用一个demo重现出来。如下:
#ifndef _USER_STORE_H_
#define _USER_STORE_H_
class UserStore
{
public:
UserStore();
virtual ~UserStore();
private:
Json::Value m_jvData;
public:
void Login();
};
#endif
//cpp文件
#include "pch.h"
#include "UserStore.h"
UserStore::UserStore()
{
}
UserStore::~UserStore()
{
}
void UserStore::Login()
{
m_jvData["a"] = "123123";
return;
}
调用如下:
...
// 全局变量:
UserStore theStore;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
....
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
theStore.Login();
break;
...
退出程序后,显示
问了deepseek和chatgpt都没啥用,后百度搜索如下内容:
发现在json_value.cpp调用下面语句时崩溃。
{
value_.string_ = valueAllocator()->duplicateStringValue( value );
}
查看 valueAllocator() ,在src\lib_json\json_value.cpp里,他是一个函数,如下:
static ValueAllocator *&valueAllocator()
{
static DefaultValueAllocator defaultAllocator;
static ValueAllocator *valueAllocator = &defaultAllocator;
return valueAllocator;
}
它是取得一个静态变量的针指。
调试发现在使用valueAllocator()时,即DefaultValueAllocator 对象指针时。DefaultValueAllocator 对象已经被析构了。
因为c++中不同的cpp文件中,全局对象和静态对象 构造和析构 顺序是不确定的。
son_value.cpp再看往下看。可以看到。
static struct DummyValueAllocatorInitializer
{
DummyValueAllocatorInitializer()
{
valueAllocator(); // ensure valueAllocator() statics are initialized before main().
}
} dummyValueAllocatorInitializer;
它的作用是确保valueAllocator()在main()函数前被调用。(注意:先构造的后析构,后构造的先析构)
但其实这样还不能确保在比其它全局对象的构造函数先调用,比其它全局对象晚析构。问题就出在这里了。
解决方案1:
不在全局对象析构函数中使用jsoncpp字符串。就没问题了。
但有时候会在全局对象析构函数保存一些数据,把它转成json格式后再存盘。所以这个解决方案,治标不治本。
解决方案2:
提前对 DefaultValueAllocator 类对象进行构造,比其它【全部对象】或【静态对象】更前构造,这样DefaultValueAllocator也会比他们更晚析构。
可以在 DummyValueAllocatorInitializer 前面加上一个编译指令 #pragma init_seg(lib) 如下:
#pragma init_seg(lib) // add by fangyukuan 2012.5.6
static struct DummyValueAllocatorInitializer
{
DummyValueAllocatorInitializer()
{
valueAllocator(); // ensure valueAllocator() statics are initialized before main().
}
} dummyValueAllocatorInitializer;
这个方案,不好的地方就是修改了第三方库。一般我们是不会去修改第三方库的。
你有更好方案吗?有,请告诉我。
其它:
我们再来看看 DefaultValueAllocator 类,都做了些什么?代码如下:
class DefaultValueAllocator : public ValueAllocator
{
public:
virtual ~DefaultValueAllocator()
{
}
virtual char *makeMemberName( const char *memberName )
{
return duplicateStringValue( memberName );
}
virtual void releaseMemberName( char *memberName )
{
releaseStringValue( memberName );
}
virtual char *duplicateStringValue( const char *value,
unsigned int length = unknown )
{
//@todo invesgate this old optimization
//if ( !value || value[0] == 0 )
// return 0;
if ( length == unknown )
length = (unsigned int)strlen(value);
char *newString = static_cast<char *>( malloc( length + 1 ) );
memcpy( newString, value, length );
newString[length] = 0;
return newString;
}
virtual void releaseStringValue( char *value )
{
if ( value )
free( value );
}
};
上面是方法一和方法二实验后均可以解决问题,我的实验方法三也能解决,随便写个值:
UserStore::UserStore()
{
m_jvData["xxxx"] = 1111;
}
运行如下代码
void main() {
// 临时future对象将在语句结束时销毁
std::async(std::launch::async, []{
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Async task completed\n";
});
std::cout << "Function continues\n";
}
在这个代码里,你会惊讶地发现 "Function continues" 会在2秒后才输出,而不是立即输出,为什么?
标准规定 (C++11及以上),当从 std::async 返回的 future 对象被销毁时:如果这是最后一个引用该共享状态的 future,且异步任务还未完成,则析构函数会阻塞等待任务完成。
设计成这样主要是为了以下的思考
异常安全:确保异步任务中的异常不会被无声丢弃
资源管理:防止任务未完成时资源提前释放
确定性:避免程序退出时仍有后台线程运行
保存future对象
std::future<void> taskFuture; // 成员变量或长期存在的对象
void startAsyncTask() {
taskFuture = std::async(std::launch::async, []{
// 长时间运行的任务
});
// future生命周期延长,不会立即阻塞
}
memset 的具体实现的代码大概如下:
//实现方式是逐字节写入
void* memset(void* dest, int ch, size_t count) {
unsigned char* p = (unsigned char*)dest;
while (count--) {
*p++ = (unsigned char)ch;
}
return dest;
}
memset 是按字节填充的,所以:
memset(a, 0, sizeof(a)) ✅(0 的字节模式是 0x00)
memset(a, -1, sizeof(a)) ✅(-1 的字节模式是 0xFF)
memset(a, 20, sizeof(a)) ❌(20 的字节模式是 0x14,但 int 可能是 0x14141414,不符合预期)
memset 会破坏 C++ 对象的内部结构(如虚表指针),导致未定义行为(UB)。
for(int i 0; i < 100; i++)
a[i] = 20;
int a[100];
std::fill(std::begin(a), std::end(a), 20); // 全部填充为 20
或者
std::fill_n(a, 100, 20); // 填充前 100 个元素为 20
#include <array>
#include <algorithm>
std::array<int, 100> a;
a.fill(20); // 全部填充为 20
或者
std::fill(a.begin(), a.end(), 20);