0

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

4

1 回答 1

3

临时对象的生命周期仅限于创建它的语句。通俗地说,语句是一行以分号结尾的代码。所有临时人员都保持活动状态,直到整个语句结束。

来自 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::pathstd::filesystem::path拥有它的内存(类似于std::string)。

for (auto const &entry : fs::directory_iterator("./")) {
    auto filename = entry.path().filename();
    std::cout << filename << '\n';
}

path也支持ostream直接输出,不需要.c_str()

于 2019-07-21T04:21:39.893 回答