Reputation: 742
Google wrote in Android ndk guides site:
Memory allocated in one library, and freed in the other, causing memory leakage or heap corruption.
EDIT
As @Galik wrote the context of this quote is:
In C++, it is not safe to define more than one copy of the same function or object in a single program. This is one aspect of the One Definition Rule present in the C++ standard.
When using a static runtime (and static libraries in general), it is easy to accidentally break this rule. For example, the following application breaks this rule:
...
In this situation, the STL, including and global data and static constructors, will be present in both libraries. The runtime behavior of this application is undefined, and in practice crashes are very common. Other possible issues include:
- Memory allocated in one library, and freed in the other, causing memory leakage or heap corruption.
- Exceptions raised in libfoo.so going uncaught in libbar.so, causing your app to crash.
- Buffering of std::cout not working properly.
Upvotes: 1
Views: 524
Reputation: 10509
I wrote that section of the doc. I've had to debug an issue where one of the standard stream objects (cout
or similar) was doubly linked into to libraries resulting in two distinct instances of the object. The constructor for the object was run twice, but twice on the same instance of the object. One object was double initialized, the other was uninitialized. When the unconstructed object was used, it would attempt to access some uninitialized memory and crash.
There's really no limit to the strangeness of undefined behavior. It's entirely possible that the bug I'm remembering was unique to the version of the compiler, linker, or loader that we were using at the time.
EDIT: Here's a repro case:
// foo.cpp
#include <stdio.h>
class Foo {
public:
Foo() { printf("this: %p\n", this); }
};
Foo foo;
// main.cpp
int main() {
}
Build with:
$ clang++ --version
clang version 7.0.0 (trunk 330210)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin
$ clang++ foo.cpp -shared -o libfoo.so
$ clang++ foo.cpp -shared -o libbar.so
$ clang++ main.cpp -L. -lfoo -lbar -rpath '$ORIGIN'
Both libfoo and libbar will be loaded, and each have their own copy of the object. The constructor will be run twice, but as you can see only one instance of the object has its constructor run; it just runs twice.
$ ./a.out
this: 0x7f9475d48031
this: 0x7f9475d48031
Upvotes: 4
Reputation: 3339
One possible reason why it's considered a mistake is because usually allocation comes with a certain initialization, and deallocation with some destruction logic.
The main danger is mismatching initialization / destruction logic.
Lets look at two different STL versions as two different and separate libraries.
Consider this: Each library lets you allocate / deallocate something. Upon resource acquisition, each library does some house-keeping on that thing in its own way, which is encapsulated (read: you don't know about it, and don't need to). What happens if the housekeeping each does is significantly different?
class Foo
{
private:
int x;
public:
Foo() : x(42) {}
};
namespace ModuleA
{
Foo* createAFoo()
{
return new Foo();
}
void deleteAFoo(Foo* foo)
{
if(foo != nullptr)
delete foo;
}
}
namespace ModuleB
{
std::vector<Foo*> all_foos;
Foo* createAFoo()
{
Foo* foo = new Foo();
all_foos.push_back(foo);
return foo;
}
void deleteAFoo(Foo* foo)
{
if(foo != nullptr)
{
std::vector<int>::iterator position = std::find(all_foos.begin(), all_foos.end(), foo);
if (position != myVector.end())
{
myVector.erase(position);
}
delete foo;
}
}
}
Question: What happens if we do the following?
Foo* foo = ModuleB::createAFoo();
ModuleA::deleteAFoo(foo);
Answer: ModuleB
now has a dangling pointer.
This can cause all sorts scary and hard to debug of issues down the line.
We're also not making all_foos
smaller, which may be considered a memory leak (the size of a pointer each time).
Question: What happens if we do the following?
Foo* foo = ModuleA::createAFoo();
ModuleB::deleteAFoo(foo);
Answer: Looks like... nothing bad happens!
But what if I removed the if (position != myVector.end())
check? Then we'd have a problem.
And an STL might do that in the name of optimization, so...
Upvotes: 4