GetDunne Wiki

Notes from the desk of Shane Dunne, software development consultant

User Tools

Site Tools


eliminate_char_arrays

Eliminating char[] arrays

Perhaps the biggest weakness of the C programming language is that it has no built-in character-string type. As a results, large chunks of the C Standard Library exist to implement strings using contiguous blocks of char(either arrays or malloc()-allocated chunks on the heap), taking advantage of the fact that the old ASCII code didn't assign a meaning to an all-zeros bit pattern, so an ASCII NUL could be used to terminate “strings” of arbitrary length. This, of course, has resulted in countless man-years of debugging frustration since 1972. I recall reading somewhere that at least one famous computer scientist suggested that C++ was one giant attempt to fix C's lack of a string type.

Unfortunately, C++ didn't have an “official” character-string type either, and because it was originally designed as a proper superset of C, programmers (myself included) kept on using the nasty old null-terminated char array techniques, including all those traditional C Standard Library functions like strcpy, memcpy, memset, and so on. Why? Because it worked, it was reasonably efficient, and the number of available C++ string classes was so bewildering (std::string, added with the C++ Standard Library, being just one more heaped onto the bonfire) that many people just preferred to stick with what they knew.

JUCE includes a perfectly acceptable new String class, which is used widely and highly consistently throughout the entire library, so it makes good sense to use it for all strings in JUCE programs.

Application in the VanillaJuce code

My original implementation of the SynthParameters class was essentially a struct which included a fixed-length char[] array for the program name, like this:

#define kMaxProgramNameLength 24
 
class SynthParameters
{
public:
    char programName[1 + kMaxProgramNameLength];    // 1 extra byte for null terminator
    ...
};

Yes (he said guiltily), I really did use an archaic C #define for the array length. I checked the old VST2.4 SDK to find the maximum program-name length, and dutifully added one more char for the null. I did this because my initial implementation of VanillaJuceAudioProcessor::getStateInformation() etc. actually used memcpy() to make a binary copy the whole parameter-block, and because the VanillaJuceAudioProcessor's programBank member variable is a simple array (also sized using a #define!), so I needed a fixed block size.

Now that I have switched over to using JUCE's very nice XML classes for serializing patch data, the fixed block size isn't as important. Moreover, since the juce::AudioProcessor::getName() function is defined to return a juce::String (presumably truncating it to 24 characters for VST2.4 hosts), I don't even have to worry about the maximum length. Hence it's a no-brainer to us a juce::String for the program name, allowing me to delete the #define:

class SynthParameters
{
public:
    String programName;
    ...
};

The resulting code changes elsewhere, to eliminate clumsy uses of things like sprintf() and juce::String::copyToUTF8(), etc. are too obvious (and embarrassing) to even list here.

Use of "const char*" for string literals

I do still use the char type in a very specific way in VanillaJuce, to declare a static array of string literals in SynthWaveform.cpp:

const char* const SynthWaveform::wfNames[] = {
    "Sine", "Triangle", "Square", "Sawtooth"
};

As Jules pointed out in his list of code issues, it's acceptable and preferable to use string literals (which the C++ compiler will interpret as being of type const char*) to initialize juce::String variables, and in place of juce::String-typed function parameters. This is because juce::String has been defined to include a constructor which takes a const char* argument, and therefore, the C++ compiler will silently invoke this constructor function to turn the given literal string into a juce::String instance automatically. In terms of the above table of waveform names, I could have made it an array of juce::String objects, but this would be redundant.

Ordinarily, I get uneasy whenever the C++ compiler silently inserts function calls in this way, because it obscures the flow of control. In this case I'm OK with it, because it is a well-established JUCE programming convention.

Eliminating unnecessary String() constructor invocations

A perfect example of why it's a good idea to avoid writing String(“xyz”) and just use the literal “xyz” arose in some code I used in SynthParameters::getXml(), which is a series of function calls like this:

xml->setAttribute(String("masterLevel"), masterLevel);

I had used the String(“xyz”) construct simply because I didn't know much about JUCE's XML functions, and had copy-pasted the code from an example I found elsewhere. When I looked, though, I noticed that the juce::XmlElement::setAttribute() function is actually declared as

void XmlElement::setAttribute (const Identifier& attributeName, const double number)

along with many related definitions, for different data-types in the second argument. In every case, however, the first argument is not a juce::String at all, but something called a juce::Identifier. Presumably, the C++ compiler is accepting my juce::String arguments by silently invoking an appropriate juce::Identifier constructor I wasn't even aware of. Depending how good its optimization is, this might be quite inefficient. The lesson is clear: whenever possible, use simple string literals, and trust that the JUCE library has defined appropriate constructors to perform whatever conversions may be necessary. All the setAttribute calls in my SynthParameters::getXml() implementation now look like this:

xml->setAttribute("masterLevel", masterLevel);
eliminate_char_arrays.txt · Last modified: 2017/09/01 16:09 by shane