Повреждение имени файла 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 ответ

Решение

Время жизни временного объекта ограничивается оператором, в котором он был создан. Неформально говоря, оператор - это строка кода, которая заканчивается точкой с запятой. Все временные остаются в живых до конца всего заявления.

Из спецификации C++:

Когда реализация вводит временный объект класса, который имеет нетривиальный конструктор ([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(),

Другие вопросы по тегам