Reputation: 19323
I have some C code which was rapidly "converted" to C++ code years back, which I now maintain. I've noticed some peculiar behavior when running it on different operating systems and building with different compilers, and could use some help diagnosing some issues I recently solved.
The code runs without issue on the following system:
After porting it over to the following system, things went south:
The offending code revolves around the following structure:
struct blk_data {
int i;
int s;
boost::mutex protectionMutex;
};
There's a block of code where a dynamic instance of this POD (plain old data, no custom methods or constructors, etc) is created via malloc()
:
struct blk_data* pData = (blk_data*)malloc(16 * sizeof(struct blk_data));
if ( pData )
//...
Please ignore the fact I'm casting the result of malloc()
. The compiler flags an error if I don't.
Later on in the code, I call:
boost::unique_lock<boost::mutex> tempLock(pData->protectionMutex);
In the case of Linux with GCC, no issues. With OSX and Clang, the program faults, noting that the mutex address passed down eventually to pthread_mutex_lock()
is invalid. Eventually, I was able to resolve this by:
new
instead of malloc()
.${CXXFLAGS} += -std=c++11
A second case where a similar issue would pop up was a chunk of code using new
, but then memset
ing the struct. It's to my understanding that this is safe provided one is using a POD struct
.
Why does this code build and run fine on GCC/Linux, yet it builds, but does not run; on Clang/OSX? Does a struct
no longer count as a POD struct
if it contains RAII-style members, or have I violated part of the C++ standard the whole time (ie: undefined behavior)? Or is this a case of more stringent checks in Clang vs GCC.
Thank you.
Upvotes: 0
Views: 327
Reputation: 69902
From the documentation of malloc
:
Allocates size bytes of uninitialized storage
Using the memory before initialising it is therefore undefined behaviour.
It works in GCC because of luck an implementation detail of glibc, specifically the fact that glibc's malloc defers to a call to nmap
to allocate new memory from the system. By default, nmap will zero out the memory to prevent information leaking from one process to another.
Documentation here: http://man7.org/linux/man-pages/man2/mmap.2.html
OSX's malloc is different (although the documentation is not explict about whether new memory is initialised or not): https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/malloc.3.html
Upvotes: 3
Reputation: 4241
Although I'm not sure if boost::mutex
is POD or not, malloc
only allocates memory but does not initialize objects, that makes protectionMutex
uninitialized.
While new
assures that default ctor of boost::mutex
is called and thus protectionMutex
is initialized.
[Update] Check boost mutex's header file, it internally uses a pointer:
#if defined(BOOST_HAS_WINTHREADS)
typedef void* cv_state;
#elif defined(BOOST_HAS_PTHREADS)
struct cv_state
{
pthread_mutex_t* pmutex;
};
#elif defined(BOOST_HAS_MPTASKS)
struct cv_state
{
};
#endif
So malloc
will certainly not initialize the pointer, while new
does.
But I really don't know why GCC works...
Upvotes: 3