Calling function twice gives segfault (in connection with char* to string conversion)

I want to expand a string ("%LOCALAPPDATA%/test.txt") with a Windows environment path. The following function in principle does the job, but calling it again with the same output string (or assigning some value to the output string before calling the function) gives a segfault.

Obviously I am making some (probably really bad) mistake by converting char* to std::string, but I don’t really understand what is going on here (beside the fact that some memory address is not available later on).

#include "processenv.h"

void expandWindowsString(const std::string &input, std::string &output)
{
    const char* in = input.c_str();
    char* out;
    ExpandEnvironmentStrings(in, out, 1024);
    output = out;
}

int main(int argc, char *argv[])
{
    std::string path;
    expandWindowsString("%LOCALAPPDATA%/test.txt", path);
    std::cout << "path is " << path << std::endl;
    //works fine so far, but if I execute the function again (with 'path') or initialising path beforehand with std::string path = "", a segfault occurs.
    expandWindowsString("%LOCALAPPDATA%/test.txt", path); // commenting out this line, makes the code work.
    std::cout << "path is " << path << "n";
}

Answer

As the documentation explains:

lpDst

A pointer to a buffer that receives the result of expanding the environment variable strings in the lpSrc buffer.

The buffer needs to be supplied by the caller, while the code simply passes an uninitialized pointer, alongside tricking the system into believing that it points at memory with a size of 1024 bytes.

A simple fix would be:

void expandWindowsString(const std::string &input, std::string &output)
{
    const char* in = input.c_str();
    char out[1024];
    ExpandEnvironmentStrings(in, out, 1024);
    output = out;
}

There’s lots of room for improvement, e.g.

  • Using the Unicode version
  • Handling errors (as explained in the documentation)
  • Repeatedly growing the output buffer in case the API call returns a value larger than the provided buffer length
  • Constructing a std::string using the pointer and length
  • Returning a value rather than using an out parameter

As for why it fails when being called a second time: That’s a meaningless question. The code exhibits undefined behavior by writing through an uninitialized pointer. With that, any outcome is possible, including the code appearing to work as expected.