使用 CxxTest 损坏的单例数据
这是一个奇怪的问题,我不知道该怎么办。
我有类似以下的内容:
struct Parms
{
const std::string value1;
const std::string value2;
std::string parm1;
std::string parm2;
Parms() : parm1(value1), parm2(value1) {}
static const Parms& getDefaults()
{
static Parms defaults;
return defaults;
}
};
我通常这样使用:
Parms myParms = Parms::getDefaults();
myParms.parm1 = "crap";
functionThatNeedsParms(myParms);
非常简单。这从来没有让我头疼,直到我开始尝试使用 CxxTest 编写使用此代码的单元测试。我在不同的文件中有两个测试套件类,当我单独运行它们时,一切都很好。
当我把它们放在一起运行时,我看到了两件不好的事情。首先,整个核心转储试图双重释放静态默认变量。其次,如果我在 defaults 消失之前一段时间查看它的内容,但在我开始使用它之后,其中的 static const std::strings 已损坏(一些字母已随机更改,尽管它始终是每次运行都一样)。
到底是怎么回事?
This is a weird problem and I'm not sure what to make of it.
I have something like the following:
struct Parms
{
const std::string value1;
const std::string value2;
std::string parm1;
std::string parm2;
Parms() : parm1(value1), parm2(value1) {}
static const Parms& getDefaults()
{
static Parms defaults;
return defaults;
}
};
Which I generally use like so:
Parms myParms = Parms::getDefaults();
myParms.parm1 = "crap";
functionThatNeedsParms(myParms);
Pretty straightforward. This has never caused me any headaches, until I started trying to write unit tests that use this code, using CxxTest. I have two test suite classes in different files, and when I run them individually, everything is great.
When I run them together, I see two bad things. First, the whole thing core dumps trying to double free the static defaults variable. Secondly, if I look at the contents of defaults some time before it dies, but after I've started using it, the static const std::strings that are in there are corrupted (some letters have randomly changed, though it is always the same on every run).
What is going on?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
双重释放和核心转储
我想我可以解释您遇到的“双重释放和核心转储”问题。我最近遇到了同样的事情,听起来你正在做和我一样的事情。
根据您的描述,您说当您“单独运行它们”时,它们工作正常,但如果您“一起运行它们”,则会出现双重释放/核心转储问题。
我发现如果同一个全局声明两次就会发生这种情况。
就我而言,我有类 foo,在一个文件中我有一个全局
class foo gFoo;
,在另一个文件中我有一个全局class foo gFoo;
。 (是的,这听起来很愚蠢,实际上我正在链接一个文件 X.cxx 以及一个也包含 X.cxx 的共享库——结果基本上是相同的。)现在,我预计编译器会对此抱怨,但显然有一些标志可以启用或禁用此检查,并且代码编译得很好。但是当程序终止并调用其所有析构函数时,它调用了 gFoo 的析构函数两次,并给了我双重释放消息以及核心转储。
鉴于您表示它独立工作正常但组合时失败,我敢打赌您在两个单独的文件中定义了全局,并且当它们自行编译时它工作正常,但是当您将它们组合起来进行单个测试时,您可能全局声明发生两次。
检查一下。
double free and core dump
I think I can explain the "double free and core dump" issue that you are having. I recently encountered the same thing and it sounds like you are doing the same thing I did.
From you description you said that when you "run them separately" they work fine but if you "run them together" you get the double free/core dump issue.
I found this to occur if the same global is declared twice.
In my case I had class foo, in one file I had a global
class foo gFoo;
and in a different file I had a globalclass foo gFoo;
. (Yeah this sounds stupid, actually I was linking against a file X.cxx as well as a shared library that also included X.cxx -- the results where essentially the same.)Now, I would have expected a compiler complaint about this, but apparently there are flags to enable or disable this check, and the code compiled fine. But when the program was terminating and calling all of its destructors, it called gFoo's destructurs twice and gave me the double free message along with a core dump.
Given that you stated it works fine independently but fails when combined, I'm betting you have the global defined in two separate files, and it works fine when they are compiled by themselves, but when you combine them to make a single test, you probably have the global declaration happening twice.
Check that out.
C 和 C++ 中的静态变量不是线程安全的。这意味着如果两个线程尝试访问您的单例对象,则可能会发生竞争条件(坏事)。解决问题的一种方法是使用线程本地存储。 pthreads 库支持这一点,并且一些编译器直接支持线程本地存储。
如果您的单例必须对所有线程都是全局的,则另一种方法是提供锁以确保一次只有一个线程可以访问您的数据。
然而,由于该问题仅在单元测试中出现。我建议不要运行多线程单元测试,除非您打算在多个线程中使用单例。
static variables in C and C++ are not thread safe. That means that race conditions (bad thing) can happen if two threads try to access your singleton object. One approach to fix your problem is to use Thread Local Storage. This is supported by the pthreads library and also some compilers directly support thread local storage.
The alternative, if your singleton MUST be global to all threads, is to provide lock to ensure that only one thread can access your data at a time.
Since however, the problem arises only in unit testing. I would suggest to not run multi-threaded unit tests unless you intend to use your singleton in multiple threads.
这高度依赖于您正在使用的编译器和平台,在没有实际看到测试的情况下,我只能猜测发生了什么。
我在您的代码中发现了一些误解:
1)你缺少复制运算符和复制构造函数
您正在复制包含
std::string
的实例,这些实例可能使用引用计数来实现。引用计数是在 std::string 的重载复制构造函数/运算符中实现的,但这些可能不会从类的隐式生成的复制构造函数中调用,因此会导致双重释放内存和其他令人讨厌的事情。复制运算符/构造函数应如下所示:2)我不太明白
value1
和value2
的存在。看来您从未初始化它们,您只是在默认构造函数中使用它们将它们的(空)内容复制到parm1
和parm2
中。您可以完全避免这种情况,因为parm1
和parm2
在调用时会自动初始化为空字符串。3)这里不需要使用单例模式。 getDefaults() 方法可以按如下方式实现:
单例模式适用于在整个程序运行过程中只有一个实例的类,而您的类似乎并非如此。
这样您就可以从多个线程安全地使用 getParms() 函数,并且智能编译器将优化隐含的附加副本。
This is highly dependent on the compiler and platform you are using and without actually seeing the tests I can only guess of what is going on.
I see some misconceptions in your code:
1) You are missing a copy operator and copy constructor
You are copying the instances that contain
std::string
which might be implemented using reference counting. The reference counting is implemented in overloaded copy constructor/operator ofstd::string
but these might not get called from the implicitly generated copy constructor of your class and therefore causing double freed memory and other nasty things. The copy operator/constructor should look like this:2) I don't quite understand the presence of
value1
andvalue2
. It seems you never initialize them, you just use them in the default constructor to copy their (empty) content intoparm1
andparm2
. You can avoid this completely asparm1
andparm2
are initialized automatically to an empty string when called.3) You do not need to use the singleton pattern here. The
getDefaults()
method could be implemented as follows:The singleton pattern is meant for classes that only have one instance through the whole program run which doesn't seem to be the case of your class.
This way you can safely use the
getParms()
function from multiple threads and a smart compiler will optimize out the implied additional copy.