使用 directory_iterator 时文件名 c_str() 损坏
Filename c_str() corruption when using 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 reports no errors.
损坏的输出:
#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 reports 159 invalid reads (of size 1) -- the exact number depends on how many files are in the directory.
这两个片段都是使用 gcc 9.1 使用以下命令编译的:
g++-9.1 test.cpp -std=c++17
临时对象的生命周期仅限于创建它的语句。通俗地说,语句就是以分号结尾的一行代码。所有临时对象都保持活动状态,直到整个语句结束。
When an implementation introduces a temporary object of a class that has a non-trivial constructor ([class.default.ctor], [class.copy.ctor]), it shall ensure that a constructor is called for the temporary object.
Similarly, the destructor shall be called for a temporary with a non-trivial destructor ([class.dtor]).
Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created.
This is true even if that evaluation ends in throwing an exception.
The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.
剖析工作示例,我们看到 operator<<
在销毁临时对象之前执行。
entry.path()
= 临时 #1
.filename()
= 临时 #2
.c_str()
从临时#2 中获取字符指针
.c_str()
传递给 std::cout
的 operator<<
,而以上所有内容仍然存在
- 调用
operator<<
获取 .c_str()
指针并执行 returns。
- 对
operator<<
的调用执行 '\n'
并且 returns.
- 所有的临时文件都被销毁了。
剖析损坏的示例,我们看到一个悬空指针:
entry.path()
= 临时 #1
.filename()
= 临时#2
.c_str()
从临时#2 中获取字符指针并存储在变量 filename
中
- End-of-statement: 所有的临时物都被破坏了。现在
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
also supports ostream
output directly,不需要 .c_str()
.
在使用 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 reports no errors.
损坏的输出:
#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 reports 159 invalid reads (of size 1) -- the exact number depends on how many files are in the directory.
这两个片段都是使用 gcc 9.1 使用以下命令编译的:
g++-9.1 test.cpp -std=c++17
临时对象的生命周期仅限于创建它的语句。通俗地说,语句就是以分号结尾的一行代码。所有临时对象都保持活动状态,直到整个语句结束。
When an implementation introduces a temporary object of a class that has a non-trivial constructor ([class.default.ctor], [class.copy.ctor]), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor ([class.dtor]). Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.
剖析工作示例,我们看到 operator<<
在销毁临时对象之前执行。
entry.path()
= 临时 #1.filename()
= 临时 #2.c_str()
从临时#2 中获取字符指针
.c_str()
传递给std::cout
的operator<<
,而以上所有内容仍然存在- 调用
operator<<
获取.c_str()
指针并执行 returns。 - 对
operator<<
的调用执行'\n'
并且 returns. - 所有的临时文件都被销毁了。
剖析损坏的示例,我们看到一个悬空指针:
entry.path()
= 临时 #1.filename()
= 临时#2.c_str()
从临时#2 中获取字符指针并存储在变量filename
中
- End-of-statement: 所有的临时物都被破坏了。现在
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
also supports ostream
output directly,不需要 .c_str()
.