juce::StringArray read access violations

Hello,

This is in Juce 7.0.5.

I feel silly posting this because I’m sure it’s something obvious, but I’ve spent enough time already…

I’m trying to create a juce::StringArray as well as two juce::Array<juce::Array<juce::StringArray>>s to manage my parameter names since I got tired of making typos with them.


Here’s my declarations in the header:

private:
    juce::StringArray tapParamNames;
    juce::Array<juce::Array<juce::StringArray>> dampingParamNames;
    juce::Array<juce::Array<juce::StringArray>> feedbackParamNames;

And the source:

//==============================================================================
// Populate database of reverb comb filter parameter names -KGK v1.6.3
void GlobeLoveler::initParamNames() {
    DBG("Initializing parameter name strings");
    
    /* 
        This throws a heap access violation: "**it** was 0xFFFFFFFFFFFFFFFF".
        See first screenshot
    */
    tapParamNames.ensureStorageAllocated(Constants::Reverb::filterCount);

    for (int i = 0; i < Constants::Reverb::filterCount; ++i) {
        juce::String tapParamName = GlobeRoomReverb::getTapParamName(i);
        DBG(tapParamName);
         
        DBG(String::formatted("tapParamNames.size (unsigned) = %d", tapParamNames.size()));
        /* 
            Before adding `tapParamNames.ensureStorageAllocated(...), I got this far.
            .add triggers a jassert, see second and third screenshots 
        */
        tapParamNames.add(tapParamName);
        
        for (int c = 0; c < Constants::Reverb::numChannels; ++c) {
            juce::StringArray newDampingParamNames;
            juce::StringArray newFeedbackParamNames;

            for (int r = 0; r < Constants::Reverb::roomCount; ++r) {
                juce::String dampParamName = GlobeRoomReverb::getDampingParamName(c, i, r);
                newDampingParamNames.add(dampParamName);
                juce::String fdbkParamName = GlobeRoomReverb::getDampingParamName(c, i, r);
                newFeedbackParamNames.add(fdbkParamName);
            }

            dampingParamNames[c][i].addArray(newDampingParamNames);
            feedbackParamNames[c][i].addArray(newFeedbackParamNames);
        }
    }
}

And the methods which generate the strings:
Header:

public:
    static juce::String getTapParamName(int i);
    static juce::String getDampingParamName(int c, int i, int r);
    static juce::String getFeedbackParamName(int c, int i, int r);

Source:

//==============================================================================
juce::String GlobeRoomReverb::getTapParamName(int i)
{
    return String::formatted("reverb.comb[0,1][%i].tap", i);
}

//==============================================================================
juce::String GlobeRoomReverb::getDampingParamName(int c, int i, int r)
{
    return String::formatted("reverb.comb[%i][%i].damping[%i]", c, i, r);
}

//==============================================================================
juce::String GlobeRoomReverb::getFeedbackParamName(int c, int i, int r)
{
    return String::formatted("reverb.comb[%i][%i].feedback[%i]", c, i, r);
}


Here’s the heap access error when I run that code:


(from debug_heap.cpp)
(what’s up with the weird characters in value??)

Full call stack:

>	ucrtbased.dll!check_bytes(const unsigned char * const first, const unsigned char value, const unsigned __int64 size) Line 194	C++
 	ucrtbased.dll!is_block_an_aligned_allocation(const void * const block) Line 252	C++
 	ucrtbased.dll!free_dbg_nolock(void * const block, const int block_use) Line 886	C++
 	ucrtbased.dll!_free_dbg(void * block, int block_use) Line 1030	C++
 	ucrtbased.dll!free(void * block) Line 32	C++
 	GlobeLoveler.exe!juce::HeapBlock<juce::String,0>::~HeapBlock<juce::String,0>() Line 137	C++
 	GlobeLoveler.exe!juce::ArrayBase<juce::String,juce::DummyCriticalSection>::setAllocatedSizeInternal(int numElements) Line 431	C++
 	GlobeLoveler.exe!juce::ArrayBase<juce::String,juce::DummyCriticalSection>::setAllocatedSize(int numElements) Line 217	C++
 	GlobeLoveler.exe!juce::ArrayBase<juce::String,juce::DummyCriticalSection>::ensureAllocatedSize(int minNumElements) Line 230	C++
 	GlobeLoveler.exe!juce::Array<juce::String,juce::DummyCriticalSection,0>::ensureStorageAllocated(int minNumElements) Line 1044	C++
 	GlobeLoveler.exe!juce::StringArray::ensureStorageAllocated(int minNumElements) Line 467	C++
 	GlobeLoveler.exe!GlobeLoveler::initParamNames() Line 729	C++

Here’s the debugging output with .ensureStorageAllocated(...) commented out:

Initializing strings
reverb.comb[0,1][0].tap
tapParamNames.size (unsigned) = -842150451

The parameter name is as I expect (side note: is it bad practice to use brackets, periods, etc. in parameter names?). But the size is obviously wrong.

Here are screenshots of the jassert and its arguments:
Screenshot 2023-11-20 172342

Screenshot 2023-11-20 172351
(from juce_ArrayBase.h)

And the full call stack:

>	GlobeLoveler.exe!juce::ArrayBase<juce::String,juce::DummyCriticalSection>::setAllocatedSize(int numElements) Line 212	C++
 	GlobeLoveler.exe!juce::ArrayBase<juce::String,juce::DummyCriticalSection>::ensureAllocatedSize(int minNumElements) Line 230	C++
 	GlobeLoveler.exe!juce::ArrayBase<juce::String,juce::DummyCriticalSection>::addImpl<juce::String>(juce::String && <toAdd_0>) Line 547	C++
 	GlobeLoveler.exe!juce::ArrayBase<juce::String,juce::DummyCriticalSection>::add(juce::String && newElement) Line 264	C++
 	GlobeLoveler.exe!juce::Array<juce::String,juce::DummyCriticalSection,0>::add(juce::String && newElement) Line 432	C++
 	GlobeLoveler.exe!juce::StringArray::add(juce::String newString) Line 140	C++
 	GlobeLoveler.exe!GlobeLoveler::initParamNames() Line 735	C++


So, why is the array initialized to a negative size? Why am I seeing negative numElements and numUsed when I try to add a string?

I also tried to initialize the array like so: tapParamNames = juce::StringArray(); even though I’ve read this is unnecessary in multiple posts. It causes the same issue as in case #1 (calling .ensureStorageAllocated → heap error).

I also returned a generic string in the getTapParamName method, to see if the problem was with String::formatted(), but this did not affect the behavior.

I thought it was probably a pointer/reference error for a while, but since I couldn’t even set the size on the array before adding objects, I’m thinking it’s deeper than that now.

Thanks for reading.

I figured out the memory errors. AudioProcessorValueTreeState parameters; was being declared before the StringArrays, so they were not initialized at the time this method was called.

Then I ran into problems with the multidimensional arrays. In the following code, I was able to add values to the juce::StringArrays, but not the juce::Array<juce::Array<juce::StringArrays>>.

Header:

    juce::StringArray tapParamNames;
    juce::Array<juce::Array<juce::StringArray>> dampingParamNames;
    juce::Array<juce::Array<juce::StringArray>> feedbackParamNames;
    AudioProcessorValueTreeState parameters;

(note the arrays are BEFORE parameters, hence they are initialized in time)

Source:


//==============================================================================
// Populate database of reverb comb filter parameter names -KGK v1.6.3
void GlobeLoveler::initParamNames() {
    DBG("Initializing parameter name strings");
    
    tapParamNames.ensureStorageAllocated(Constants::Reverb::filterCount);
    dampingParamNames.ensureStorageAllocated(Constants::Reverb::numChannels);
    feedbackParamNames.ensureStorageAllocated(Constants::Reverb::numChannels);
    
    for (int i = 0; i < Constants::Reverb::filterCount; ++i) {
        juce::String tapParamName = GlobeRoomReverb::getTapParamName(i);
        tapParamNames.add(tapParamName);
        DBG(tapParamNames[i]);

        DBG(String::formatted("tapParamNames.isEmpty = %i", tapParamNames.isEmpty()));
        DBG(String::formatted("tapParamNames.size = %i", tapParamNames.size()));

        for (int c = 0; c < Constants::Reverb::numChannels; ++c) {
            dampingParamNames[c].ensureStorageAllocated(Constants::Reverb::filterCount);
            feedbackParamNames[c].ensureStorageAllocated(Constants::Reverb::filterCount);

            for (int r = 0; r < Constants::Reverb::roomCount; ++r) {
                dampingParamNames[c][i].ensureStorageAllocated(Constants::Reverb::roomCount);
                feedbackParamNames[c][i].ensureStorageAllocated(Constants::Reverb::roomCount);

                juce::String dampParamName = GlobeRoomReverb::getDampingParamName(c, i, r);
                dampingParamNames[c][i].add(dampParamName);
                DBG(dampParamName);

                juce::String fdbkParamName = GlobeRoomReverb::getFeedbackParamName(c, i, r);
                feedbackParamNames[c][i].add(fdbkParamName);
                DBG(fdbkParamName);
            }

            DBG(String::formatted("dampingParamNames[c][i].isEmpty = %i", dampingParamNames[c][i].isEmpty()));
            DBG(String::formatted("dampingParamNames[c][i].size = %i", dampingParamNames[c][i].size()));

            DBG(String::formatted("feedbackParamNames[c][i].isEmpty = %i", feedbackParamNames[c][i].isEmpty()));
            DBG(String::formatted("feedbackParamNames[c][i].size = %i", feedbackParamNames[c][i].size()));
        }
    }
}

tapParamNames was populated, but dampingParamNames and feedbackParamNames were not.

If anyone has insight as to why that may be, I’d love to hear it; at any rate, I switched back to simple arrays of strings, since my sizes are all fixed. This is what I attempted in the first place, buut had memory for the same reason, declaration order.

    juce::String tapParamNames[Constants::Reverb::filterCount];
    juce::String dampingParamNames[Constants::Reverb::numChannels][Constants::Reverb::filterCount][Constants::Reverb::roomCount];
    juce::String feedbackParamNames[Constants::Reverb::numChannels][Constants::Reverb::filterCount][Constants::Reverb::roomCount];

Cheers!