С какой стати моя функция чтения файлов размещает нулевые терминаторы там, где должны быть избыточные CR LF-каретки? - PullRequest
0 голосов
/ 27 марта 2020

Сегодня я попытался собрать простой класс шейдеров OpenGL, который загружает текст из файла, выполняет небольшой анализ для создания пары вершинных и фрагментных шейдеров в соответствии с некоторым (довольно приятным) пользовательским синтаксисом (например, запись «.varying [type] [name];» позволит вам определить переменную переменную в обоих шейдерах, при этом писать ее только один раз, то же самое с «.version»,), затем скомпилировать шейдерную программу OpenGL, используя два, затем пометить класс шейдера как «готовый», если и только если код шейдера скомпилирован правильно.

Теперь я сделал все это, но потом столкнулся с самыми странными (и, честно говоря, довольно страшными) проблемами. Я все настроил, объявил новый 'tt :: Shader' с некоторым файлом, содержащим действительный код шейдера, только чтобы он сказал мне, что шейдер недействителен, но затем выдал мне пустую строку, когда я спросил, что это за ошибка (что означает OpenGL дал мне пустую строку, так как именно там он и получил ее.)

Я попробовал еще раз, на этот раз с явно недействительным кодом шейдера, и хотя он определил, что шейдер был недействительным, он все равно ничего не дал мне в терминах из того, что было ошибкой, просто пустая строка (из которой я предположил, что очевидно, что часть идентификации ошибки также была такой же, как и раньше.)

Смущенный, я переписал оба шейдера, действительный и неверный, вручную, как строка, снова компилирует классы со строкой напрямую, без доступа к файлу. При этом ошибка исчезла, первая скомпилирована правильно, а вторая не удалась, но правильно определила, что это за ошибка.

Еще больше запутавшись, я начал сравнивать строки из файлов с теми, которые написал сам. Оказывается, первые были чуть длиннее лестницы, несмотря на то, что печатали так же. После небольшого подсчета я понял, что эти символы должны быть Windows CR LF, заканчивающиеся символами переноса строк, которые были обрезаны в процессе импорта.

Чтобы проверить это, я взял рукописные строки, вставил каретки, где они будут отрезаны, и снова запустил мои тесты сравнения строк. На этот раз он оценил, что длины одинаковы, но также сказал мне, что эти два числа все еще не равны, что было довольно загадочно.

Итак, я написал простой for-l oop для итерации символы двух строк и печатают затем каждый рядом друг с другом, и приводят к целым числам, чтобы я мог видеть их значения индекса. Я запустил программу, просмотрел (довольно длинный) список и пришел к весьма проницательному, хотя и менее ясному ответу: скрытые символы были в нужных местах, но они не были каретами ... они были нулевыми терминаторами!

Вот код для функции чтения файлов, которую я использую. Ничего особенного, просто стандартные вещи из библиотеки.

// Attempts to read the file with the given path, returning a string of its contents.
// If the file could not be found and read, an empty string will be returned.
// File strings are build by reading the file line-by-line and assembling a single with new lines placed between them.
// Given this line-by-line method, take note that it will copy no more than 4096 bytes from a single line before moving on.
inline std::string fileRead(const std::string& path) {


    if (!tt::fileExists(path))
        return "";
    std::ifstream a;
    a.open(path);
    std::string r;
    const tt::uint32 _LIMIT = 4096;
    char r0[_LIMIT];

    tt::uint32 i = 0;
    while (a.good()) {
        a.getline(r0, _LIMIT);
        if (i > 0)
            r += "\n";
        i++;
        r += std::string(r0, static_cast<tt::uint32>(a.gcount()));
    }

    // TODO: Ask StackOverflow why on earth our file reading function is placing null characters where excess carriages should go.

    for (tt::uint32 i = 0; i < r.length(); i++)
        if (r[i] == '\0')
            r[i] = '\r';
    a.close();
    tt::printL("Reading file '" + path + "' ...");
    return r;
}

Если бы вы все могли прочитать и рассказать, что, черт возьми, происходит с этим, это было бы здорово, потому что я в полная потеря за то, что он делает с моей строкой, чтобы вызвать это.

Наконец, я понимаю, почему нулевые терминаторы не показывались мне, но делали для OpenGL, лестница использовала C -строки в то время как я просто делал все с объектами std :: string, где хранил вещи, основанные на длине, учитывая, что они в значительной степени просто причудливые объекты std :: vector.

1 Ответ

1 голос
/ 27 марта 2020

Прочитайте документацию для std::string конструктора. Конструктор std::string(const char*, size_t n) создает строку размером n независимо от ввода. Он может содержать нулевой символ внутри или даже больше 1. Обратите внимание, что размер std::string не включает нулевой символ (так что str[str.size()] == '\0' всегда).

Очевидно, что код просто копирует нулевой символ из выходного буфера функции getline.

Зачем это нужно? Go до gcount() функция документация - возвращает количество извлеченных символов последней операцией. То есть он включает в себя извлеченный символ \n, который заменяется в выводе на \0 вуаля. Ровно на одно число больше, что просит конструктор.

Так что, чтобы исправить это, просто замените:

r += std::string(r0, static_cast<tt::uint32>(a.gcount()-1));

Или вы могли бы просто использовать getline() с std::string в качестве ввода вместо буфера - и ничего из этого не произошло бы.

...