POSIX函数S_ISDIR偶尔会对我说谎 它告诉我一个目录存在,当它显然没有。
这是一个说明问题的小程序:
#include <sys/stat.h>
#include <dirent.h>
#include <string>
#include <iostream>
#include <iomanip>
bool Is_Directory(const char* path_to_file){
struct stat fileInfo;
std::cout << lstat(path_to_file, &fileInfo) << " ";
return S_ISDIR(fileInfo.st_mode);
}
int main(){
std::cout << std::boolalpha;
std::cout << Is_Directory("folder") << '\n';
std::cout << Is_Directory("folder") << '\n';
std::cout << Is_Directory("folder") << '\n';
std::cout << Is_Directory("folder") << '\n';
std::cout << Is_Directory("folder") << '\n';
std::cout << Is_Directory("folder") << '\n';
}
如果我很快运行这个程序(很多次),我会看到以下输出:
$./main
-1 false
-1 false
-1 false
-1 false
-1 false
-1 false
$./main
-1 false
-1 false
-1 true
-1 true
-1 true
-1 true
$./main
-1 false
-1 false
-1 false
-1 false
-1 false
-1 false
查看该函数如何突然返回true
,即使该目录不存在。
但奇怪的是,如果我将程序置于无限循环的检查中,它将继续说该目录不存在。只有通过一次又一次地快速连续运行程序才能发现问题。
以下是我迄今为止所尝试的内容:
检查代码: 代码似乎没有错。
Macro: int S_ISDIR (mode_t m)
This macro returns non-zero if the file is a directory.
lstat
的错误代码始终为-1,因此我不认为偶尔出现错误填充统计信息。
阅读文档:
我在lstat
上看到了以下文档:
lstat() is identical to stat(), except that if pathname is a symbolic
link, then it returns information about the link itself, not the file
that it refers to.
我并不完全理解这个含义,但也许它与我的问题有关?
所以我决定使用常规stat()
,我仍然会看到同样的问题。
不同的编译器:
我已经尝试了两种不同的编辑器,其中包含警告和消毒剂
g++
和clang++
。两者都表现出同样的问题。
是否需要使用C编译器编译?
我在vanilla C中重新编写了程序(但仍然使用g ++ / clang ++编译它)。
#include <sys/stat.h>
#include <dirent.h>
#include <stdio.h>
bool Is_Directory(const char* path_to_file){
struct stat fileInfo;
printf("%d ",lstat(path_to_file, &fileInfo));
return S_ISDIR(fileInfo.st_mode);
}
int main(){
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
}
突然之间,问题就消失了。我一次又一次地快速启动程序,但它始终正确地报告目录不存在。
我切换回C ++代码,再次运行我的测试。果然,偶尔会出现误报。
是系统标题吗?
我把C ++标题放入C版本。程序仍然没有问题。
是std :: cout吗?
也许std::cout
速度较慢,这就是我看到问题的原因......或者可能是完全不相关的。也许使用std::cout
间接保留导致问题的二进制文件中的内容。或std::cout
是否在全球范围内为我的计划环境做些什么?
我在这里黑暗中射击。
我尝试了以下内容:
#include <sys/stat.h>
#include <dirent.h>
#include <stdio.h>
#include <iostream>
bool Is_Directory(const char* path_to_file){
struct stat fileInfo;
printf("%d ",lstat(path_to_file, &fileInfo));
return S_ISDIR(fileInfo.st_mode);
}
int main(){
std::cout << "test" << std::endl;
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
printf("%d\n",Is_Directory("folder"));
}
啊哈!
$./main
test
-1 0
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 0
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 1
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 0
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 1
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 0
-1 0
-1 0
-1 0
-1 0
-1 0
现在它只是第一次有时会返回true的检查
就像std::cout
在某种程度上弄乱S_ISDIR
一样,但在调用S_ISDIR
后,它不会搞砸下一次调用S_ISDIR
。
调查来源:
我在S_ISDIR
中找到了/usr/include/sys
的源代码:
/* Test macros for file types. */
#define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask))
#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
S_ISDIR
似乎只是一个帮手,无论目录是否存在,已经从stat()
决定了。 (同样,我已经尝试了stat
和lstat
。我想我会使用fstat
吗?我不这么认为。我在网上找到了人们使用的其他例子
S_ISDIR
与我的示例代码相同)。
同样,当我将代码放入使用std::cout
进行检查和打印的无限循环时,它不会显示症状。这让我相信问题只发生在程序的开头,但我想这似乎也不正确,因为如果你看看我的原始输出,那就是:
$./main
-1 false
-1 false
-1 true
-1 true
-1 true
-1 true
操作系统/硬盘/系统库/编译器:
我的机器有问题吗?
不,我在Ubuntu 16.04.1 LTS
。我去了另一台机器CentOS 6.5
并使用较早版本的g++
试用了这个。相同的结果。
所以我的代码很糟糕。
隔离问题:
我已经简化了这个问题 该程序 有时 会返回错误。
#include <sys/stat.h>
#include <iostream>
bool Is_Directory(const char* path_to_file){
struct stat fileInfo;
stat(path_to_file, &fileInfo);
return S_ISDIR(fileInfo.st_mode);
}
int main(){
std::cout << std::endl;
return Is_Directory("folder");
}
此程序将 从不 返回错误。
#include <sys/stat.h>
#include <iostream>
bool Is_Directory(const char* path_to_file){
struct stat fileInfo;
stat(path_to_file, &fileInfo);
return S_ISDIR(fileInfo.st_mode);
}
int main(){
return Is_Directory("folder");
}
为什么在有时存在的目录中刷新缓冲区? 实际上,如果我只刷新缓冲区,问题就会消失。
此程序将 从不 返回错误。
#include <sys/stat.h>
#include <iostream>
bool Is_Directory(const char* path_to_file){
struct stat fileInfo;
stat(path_to_file, &fileInfo);
return S_ISDIR(fileInfo.st_mode);
}
int main(){
std::cout.flush();
return Is_Directory("folder");
}
嗯,这可能是因为它没有什么可以冲洗的。
只要我冲洗至少一个角色,我们就会再次遇到问题 这是真正的MCVE:
#include <sys/stat.h>
#include <iostream>
int main(){
std::cout << std::endl;
struct stat fileInfo;
stat("f", &fileInfo);
return S_ISDIR(fileInfo.st_mode);
}
同样,无限循环不起作用 该程序将 从不 返回(假设第一次尝试时运气好):
#include <sys/stat.h>
#include <iostream>
int main(){
while (true){
std::cout << std::endl;
struct stat fileInfo;
stat("f", &fileInfo);
if(S_ISDIR(fileInfo.st_mode)) return 0;
}
}
因此,重新启动流程以及刷新时会出现问题? 我抛弃了集会,但这对我来说并不重要。
g++ -std=c++1z -g -c a.cpp
objdump -d -M intel -S a.o > a.s
a.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
#include <sys/stat.h>
#include <iostream>
int main(){
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 81 ec a0 00 00 00 sub rsp,0xa0
b: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28
12: 00 00
14: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
18: 31 c0 xor eax,eax
std::cout << std::endl;
1a: be 00 00 00 00 mov esi,0x0
1f: bf 00 00 00 00 mov edi,0x0
24: e8 00 00 00 00 call 29 <main+0x29>
struct stat fileInfo;
stat("f", &fileInfo);
29: 48 8d 85 60 ff ff ff lea rax,[rbp-0xa0]
30: 48 89 c6 mov rsi,rax
33: bf 00 00 00 00 mov edi,0x0
38: e8 00 00 00 00 call 3d <main+0x3d>
return S_ISDIR(fileInfo.st_mode);
3d: 8b 85 78 ff ff ff mov eax,DWORD PTR [rbp-0x88]
43: 25 00 f0 00 00 and eax,0xf000
48: 3d 00 40 00 00 cmp eax,0x4000
4d: 0f 94 c0 sete al
50: 0f b6 c0 movzx eax,al
53: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8]
57: 64 48 33 14 25 28 00 xor rdx,QWORD PTR fs:0x28
5e: 00 00
60: 74 05 je 67 <main+0x67>
62: e8 00 00 00 00 call 67 <main+0x67>
67: c9 leave
68: c3 ret
0000000000000069 <_Z41__static_initialization_and_destruction_0ii>:
69: 55 push rbp
6a: 48 89 e5 mov rbp,rsp
6d: 48 83 ec 10 sub rsp,0x10
71: 89 7d fc mov DWORD PTR [rbp-0x4],edi
74: 89 75 f8 mov DWORD PTR [rbp-0x8],esi
77: 83 7d fc 01 cmp DWORD PTR [rbp-0x4],0x1
7b: 75 27 jne a4 <_Z41__static_initialization_and_destruction_0ii+0x3b>
7d: 81 7d f8 ff ff 00 00 cmp DWORD PTR [rbp-0x8],0xffff
84: 75 1e jne a4 <_Z41__static_initialization_and_destruction_0ii+0x3b>
extern wostream wclog; /// Linked to standard error (buffered)
#endif
//@}
// For construction of filebuffers for cout, cin, cerr, clog et. al.
static ios_base::Init __ioinit;
86: bf 00 00 00 00 mov edi,0x0
8b: e8 00 00 00 00 call 90 <_Z41__static_initialization_and_destruction_0ii+0x27>
90: ba 00 00 00 00 mov edx,0x0
95: be 00 00 00 00 mov esi,0x0
9a: bf 00 00 00 00 mov edi,0x0
9f: e8 00 00 00 00 call a4 <_Z41__static_initialization_and_destruction_0ii+0x3b>
a4: 90 nop
a5: c9 leave
a6: c3 ret
00000000000000a7 <_GLOBAL__sub_I_main>:
a7: 55 push rbp
a8: 48 89 e5 mov rbp,rsp
ab: be ff ff 00 00 mov esi,0xffff
b0: bf 01 00 00 00 mov edi,0x1
b5: e8 af ff ff ff call 69 <_Z41__static_initialization_and_destruction_0ii>
ba: 5d pop rbp
bb: c3 ret
我尝试使用stat源代码,但相当丢失
C ++源代码更容易理解。以下是/bits/ostream.tcc
的冲洗功能:
template<typename _CharT, typename _Traits>
basic_ostream<_CharT, _Traits>&
basic_ostream<_CharT, _Traits>::
flush()
{
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 60. What is a formatted input function?
// basic_ostream::flush() is *not* an unformatted output function.
ios_base::iostate __err = ios_base::goodbit;
__try
{
if (this->rdbuf() && this->rdbuf()->pubsync() == -1)
__err |= ios_base::badbit;
}
__catch(__cxxabiv1::__forced_unwind&)
{
this->_M_setstate(ios_base::badbit);
__throw_exception_again;
}
__catch(...)
{ this->_M_setstate(ios_base::badbit); }
if (__err)
this->setstate(__err);
return *this;
}
似乎致电pubsync()
,引导我sync()
中的/ext/stdio_sync_filebuf.h
方法:
sync()
{ return std::fflush(_M_file); }
virtual std::streampos
seekoff(std::streamoff __off, std::ios_base::seekdir __dir,
std::ios_base::openmode = std::ios_base::in | std::ios_base::out)
{
std::streampos __ret(std::streamoff(-1));
int __whence;
if (__dir == std::ios_base::beg)
__whence = SEEK_SET;
else if (__dir == std::ios_base::cur)
__whence = SEEK_CUR;
else
__whence = SEEK_END;
#ifdef _GLIBCXX_USE_LFS
if (!fseeko64(_M_file, __off, __whence))
__ret = std::streampos(ftello64(_M_file));
#else
if (!fseek(_M_file, __off, __whence))
__ret = std::streampos(std::ftell(_M_file));
#endif
return __ret;
}
virtual std::streampos
seekpos(std::streampos __pos,
std::ios_base::openmode __mode =
std::ios_base::in | std::ios_base::out)
{ return seekoff(std::streamoff(__pos), std::ios_base::beg, __mode); }
}; sync()
{ return std::fflush(_M_file); }
virtual std::streampos
seekoff(std::streamoff __off, std::ios_base::seekdir __dir,
std::ios_base::openmode = std::ios_base::in | std::ios_base::out)
{
std::streampos __ret(std::streamoff(-1));
int __whence;
if (__dir == std::ios_base::beg)
__whence = SEEK_SET;
else if (__dir == std::ios_base::cur)
__whence = SEEK_CUR;
else
__whence = SEEK_END;
#ifdef _GLIBCXX_USE_LFS
if (!fseeko64(_M_file, __off, __whence))
__ret = std::streampos(ftello64(_M_file));
#else
if (!fseek(_M_file, __off, __whence))
__ret = std::streampos(std::ftell(_M_file));
#endif
return __ret;
}
virtual std::streampos
seekpos(std::streampos __pos,
std::ios_base::openmode __mode =
std::ios_base::in | std::ios_base::out)
{ return seekoff(std::streamoff(__pos), std::ios_base::beg, __mode); }
};
据我所知,C ++正在将工作归于std::fflush
。
经过多次测试后,我发现了这一点
来自fflush()
的{{1}}出现了问题,但来自<iostream>
的{{1}}没有。
我试图从fflush()
向后追溯,但我认为我遇到了源代码边界。
<stdio.h>
所以它一定是我与之联系的东西?
fflush()
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int fflush (FILE *__stream);
__END_NAMESPACE_STD
#ifdef __USE_MISC
/* Faster versions when locking is not required.
This function is not part of POSIX and therefore no official
cancellation point. But due to similarity with an POSIX interface
or due to the implementation it is a cancellation point and
therefore not marked with __THROW. */
extern int fflush_unlocked (FILE *__stream);
#endif
//exhibits the problem
#include <sys/stat.h>
#include <iostream>
int main(){
printf("\n");fflush(stdout);
struct stat fileInfo;
stat("f", &fileInfo);
return S_ISDIR(fileInfo.st_mode);
}
g++ -std=c++11 -o main a.cpp
ldd main
linux-vdso.so.1 => (0x00007ffdc878e000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1300c00000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1300837000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f130052d000)
/lib64/ld-linux-x86-64.so.2 (0x000055bace4bc000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1300316000)
我认为//works correctly
#include <sys/stat.h>
#include <stdio.h>
int main(){
printf("\n");fflush(stdout);
struct stat fileInfo;
stat("f", &fileInfo);
return S_ISDIR(fileInfo.st_mode);
}
不适合使用g++ -std=c++11 -o main a.cpp
ldd main
linux-vdso.so.1 => (0x00007ffd57f7c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f482dc6c000)
/lib64/ld-linux-x86-64.so.2 (0x000055828633a000)
,但libstdc++.so.6
是?我应该单独构建使用S_ISDIR
的代码,然后将其与C ++代码链接吗?我怎样才能更快地发现这样的问题?我仍然不明白发生了什么。我是否因为链接了错误的库而践踏/观察错误的内存?你会如何解决这个问题?
答案 0 :(得分:3)
如果系统调用成功,您只能分析struct stat
lstat()
数据集。如果失败,则返回-1
(它可能根本没有修改fileInfo
中的数据 - 尽管这些值是不确定的)。你在fileInfo.st_mode
得到的是垃圾,因为lstat()
失败了 - 它可以随心所欲地为S_ISDIR()
返回true或false。
因此,您的第一个示例显示lstat()
每次都失败,因此对struct stat
的任何分析都是徒劳的;它没有设置为任何确定的值,任何结果都可以。
我认为,相同的论点适用于所有示例代码。
stat()
和lstat()
之间的区别在于,如果提供的名称是符号链接,则stat()
系统调用将引用符号链接远端的文件系统对象(假设有一个;如果符号链接指向不存在的对象则失败),而lstat()
系统调用则指代符号链接本身。当名称不是符号链接时,这两个调用将返回相同的信息。