Повреждение имени файла c_str() при использовании directory_iterator
При просмотре всех файлов в каталоге с directory_iterator
хранение c_str()
имя файла перед его использованием приводит к неверному чтению (и выводу мусора).
Это кажется довольно странным для меня.
Примеры кода:
Работает:
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
for (auto const &entry : fs::directory_iterator("./")) {
std::cout << entry.path().filename().c_str() << '\n';
}
}
Valgrind сообщает об отсутствии ошибок.
Поврежденный вывод:
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
for (auto const &entry : fs::directory_iterator("./")) {
auto filename = entry.path().filename().c_str();
std::cout << filename << '\n';
}
}
valgrind сообщает о 159 неверных чтениях (размером 1) - точное число зависит от количества файлов в каталоге.
Оба эти фрагмента были скомпилированы с помощью gcc 9.1 с помощью команды:g++-9.1 test.cpp -std=c++17
1 ответ
Время жизни временного объекта ограничивается оператором, в котором он был создан. Неформально говоря, оператор - это строка кода, которая заканчивается точкой с запятой. Все временные остаются в живых до конца всего заявления.
Когда реализация вводит временный объект класса, который имеет нетривиальный конструктор ([class.default.ctor], [class.copy.ctor]), он должен гарантировать, что конструктор вызывается для временного объекта. Точно так же деструктор должен быть вызван для временного с нетривиальным деструктором ([class.dtor]). Временные объекты уничтожаются как последний шаг в оценке полного выражения ([intro.execution]), которое (лексически) содержит точку, в которой они были созданы. Это верно, даже если эта оценка заканчивается выдачей исключения. Вычисления значений и побочные эффекты уничтожения временного объекта связаны только с полным выражением, а не с каким-либо конкретным подвыражением.
Разбирая рабочий пример, мы видим, что operator<<
выполняет до уничтожения временных.
entry.path()
= временный № 1.filename()
= временный № 2.c_str()
получает указатель символа из временного #2.c_str()
перешел вoperator<<
изstd::cout
пока все вышеперечисленное еще живо- Позвонить
operator<<
принимая.c_str()
указатель выполняется и возвращает. - Позвонить
operator<<
принятие'\n'
выполняется и возвращается. - Все временные уничтожены.
Рассекая разбитый пример, мы видим висячий указатель:
entry.path()
= временный № 1.filename()
= временный № 2.c_str()
получает указатель символа из временного #2 и сохраняется в переменнойfilename
- Конец заявления: все временные уничтожены. В настоящее время
filename
указывает на удаленную память - это висячий указатель. - Позвонить
operator<<
передается висячий указатель, который разыменовывается, как если бы он был допустимой строкой = неопределенное поведение.
Вы можете извлечь локальную переменную без искажения, удалив .c_str()
, который делает переменную filename
объект типа std::filesystem::path
,std::filesystem::path
владеет своей памятью (аналогичноstd::string
).
for (auto const &entry : fs::directory_iterator("./")) {
auto filename = entry.path().filename();
std::cout << filename << '\n';
}
path
также поддерживаетostream
вывод напрямую, без необходимости .c_str()
,