Progress! Thank you, https://stackoverflow.com/users/6879826/ad-absurdum, for putting me on the right path! After a few tweaks, converting things to/from wchar_t, it's now returning the full names (with the \n characters, but that's a separate problem).
Here's the revised load_charlist code (only code that's changed):
int load_charlist()
{
    errno_t err;
    wchar_t tempstr[48]; //ML_CHARNAME * 2, to be on the safe side
    int i = 0;
    size_t j;
    err = fopen_s(&inputfile, "Saves//charlist.dat", "r, ccs=UTF-8");
    if (!inputfile) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("Can't open Saves\\charlist.dat.  Exiting.");
        return 1;
    }
    charcount = 0;
    while (!feof(inputfile) && !ferror(inputfile))
    {
        if (fgetws(tempstr, _countof(tempstr), inputfile) != NULL)
        {
            trim_trailing_chars(tempstr, "\r\n");
            if (strcmp(tempstr, "none") != 0)
            {
                ++charcount;
                wcstombs_s(&j, charlist[i], (size_t)48, tempstr, (size_t)48 - 1);
                i++;
            }
        }
        strcpy_s(tempstr, _countof(tempstr), "");
    }
    err = fclose(inputfile);
}