Initializing global objects as a pointer is the only way that succeeds

162 views Asked by At

In a JNI .cpp file I have a struct with a SoundTouch* (SoundTouch is a C++ audio processing that I am wrapping for use in an Android project) and I initialize a vector of the structs as global objects like this:

struct SoundTouchExt
{
    SoundTouch* sTouch;
    queue<signed char>* fBufferOut;
    int channels;
    int sampleRate;
    float tempoChange;
    int pitchSemi;
    int bytesPerSample;

    SoundTouchExt()
    {
        sTouch = new SoundTouch();
        fBufferOut = new queue<signed char>();
    }
};

const int MAX_TRACKS = 16;

vector<SoundTouchExt> sProcessors(MAX_TRACKS);

This works, at least if I only use one of the SoundTouchExt objects at a time in my program (that's sort of a different story, but possibly related - with multiple instances in play causes distorted output).

However, if I declare it like this SoundTouch sTouch;, comment out the new and change the use of it accordingly ( -> to .), pointers to references, I compile fine but I get a FAULT 11 (seg fault) as soon as the program trys to use the object.

Here's where that happens:

...
    SoundTouchExt& soundTouch = sProcessors.at(track);
    setup(soundTouch, channels, samplingRate, bytesPerSample, tempo, pitchSemi);
}

static void setup(SoundTouchExt& soundTouch, int channels, int sampleRate, int bytesPerSample, float tempoChange, float pitchSemi)
{
    SoundTouch& sTouch = soundTouch.sTouch;

    soundTouch.channels = channels;
    soundTouch.sampleRate = sampleRate;
    soundTouch.bytesPerSample = bytesPerSample;
    soundTouch.tempoChange = tempoChange;
    soundTouch.pitchSemi = pitchSemi;

    sTouch.setSampleRate(sampleRate);
    sTouch.setChannels(channels);
...
}

With a little research, I'm thinking this could be an instance of the static intialization order fiasco. I don't see any global variables in the library source code, but I don't know enough about C++ to know what else to look for.

What can my observation suggest about the library (or maybe I'm not doing something correctly)?

1

There are 1 answers

1
TheUndeadFish On BEST ANSWER

I believe your SoundTouch struct/class has an issue in its copy constructor and/or assignment operator. Or you haven't even written those but need to.

Why do I say this when I can't even see the code for SoundTouch? Well...

Your SoundTouchExt has problems with how it manages its sTouch member (and fBufferOut as well). Each instance creates its own sTouch, but you don't have a copy constructor or assignment operator to deal with sTouch members whenever your objects get copied. The defaults provided by the compiler will simply do a shallow member-wise copy. So if one SoundTouchExt object ever gets assigned to another, then they will both end up with sTouch pointers pointering to the same SoundTouch. I doubt you ever intended for that to happen. However since you also don't have a destructor to clean up those allocations, you might get away with this situation for a while (since leaking memory would be easy to overlook).

And it looks like you are indeed get happening to get away with it while using vector<SoundTouchExt>. A vector manages an internal array. When you add entries to a vector, it may sometimes run out of room in its current array and thus need to create a new array to hold the additional entry. In doing so, it has to copy all the entries from the old array to the new one. So that uses the copy constructor and/or assignment operator of SoundTouchExt. You don't notice this because the situation of two SoundTouchExt instances using the same SoundTouch only exists briefly before the one in the old array gets destroyed. And because SoundTouchExt lacks a destructor, there's nothing to cause a problem.

Now consider how things change when the sTouch member is an actual SoundTouch instance rather than a pointer. In that case, when a SoundTouchExt object is copied and thus the sTouch member is copied, that means the compiler will use the SoundTouch copy constructor / assignment operator. And we know your vector can cause that to happen.

Since your SoundTouchExt has the issues I've described with copying, I suspect your SoundTouch also has issues. If that is the case, then by the time you get around to trying to use an sTouch member, it has presumably already been copied and thus caused some kind of problem. That problem then leads to your crash.

So to fix things, you have a few options:

  • Make sure all the relevant objects act correctly when being copied. This probably means implementing copy constructors and assignment operators. And in that case you most likely should implement a destructor, as per the Rule of Three.
  • Or disable their copy constructors and assignment operators (declare them as private but don't implement) so they can't accidentally be used. That then might require some other refactoring, such as your vector usage. Although if you have C++11 features available, you might be able to make use of move operators/constructors so that your objects can work in standard containers like they do now but properly transfer their members when appropriate. A destructor is probably still necessary either way.
  • Or replace those raw pointers with certain kinds of smart pointers (like unique_ptr) which can automatically manage anything allocated with new without you having to write any additional code.