Cloud
Cloud

Reputation: 19323

Memory corruption when building (questionable) example with Clang instead of GCC

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:

A second case where a similar issue would pop up was a chunk of code using new, but then memseting 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

Answers (2)

Richard Hodges
Richard Hodges

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

Mine
Mine

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

Related Questions