最近踩的一个坑,填这个坑大概花了我1个小时,记录下来希望可以帮助大家避免跟我一样(捂脸)。事情是这样,我最近需要一个c++的简单的记log到文件的功能,就是一个比较临时的功能,我第一反应就是用c++文件流标准库简单实现。很自然我就凭着记忆写下了下边的代码(因为算是临时的一个功能,错误判断也就没有加了,一切简单粗暴,切勿模仿):

 

std::ofstream simpleLog;simpleLog.open(logFile,  std::ios::app | std::ios::trunc);

【代码框可左右滑动以查看更多】

 

一切很简单完美,对吧?嘿嘿,我对自己写的代码还是比较自恋的(如果你看出问题,就不用往下看了,顺便记得留言让我膜拜一下,)。接下来我就开始写了不少用simpleLog进行log输出的相关代码,写完很潇洒地点了下运行按钮。我一边得意地等待编译和运行完成,一边准备去看log输出。等程序运行结束后发现,咦?怎么没有log文件生成?什么鬼?没办法,开始一步一步地加上文件操作的错误判断,然后发现simpleLog.open()函数调用后,simpleLog.bad()函数就返回true了。小问题,这还难得到我?肯定是文件路径或文件权限不对,我简单检查了下,没有问题呀。到了这个时候,感觉开始有点不可思议了,难道又要来玄学了?不不不,还是要相信科学。因为在Windows下开发,我又在simpleLog.open()失败后的分支里边使用Windows的错误码获取函数GetLastErrror()获取Windows的系统错误码。因为文件操作主要是系统api调用,所以猜测是系统api调用失败了。打印出来后发现错误码是2,在Windows的System Error Codes中查了下2的定义如下:

ERROR_FILE_NOT_FOUND2 (0x2)The system cannot find the file specified.

【代码框可左右滑动以查看更多】

 

找不到文件?我再一个一个字符地检查了一遍文件路径和文件名,还是没有错误呀(开始有点进入玄学状态)。Windows系统又不开源,对我来说就是个黑盒,还是要找我们的老朋友Linux。我在Linux下快速撸了个test.cpp作为验证的小demo,并在判断simpleLog.open()失败后通过strerror(errno)获取错误信息,代码如下:

int main(){    ofstream f;    f.open("./abc.txt", ios::app | ios::trunc);    if (f.bad()) {        cout << "open bad:" << strerror(errno) << endl;    } else {        cout << "open good" << endl;    }      f << "write something" << endl;    f.close();}

 

然后我们来编译并运行,看看输出:

$g++ -Wall -g -std=c++11 test.cpp -o test && ./testopen good$ cat abc.txtcat: abc.txt: No such file or directory

咦?输出"open good",但是当前目录下却找不到输出的文体abc.txt,真是见鬼了。好在我们的老朋友Linux对我们很开放,我们想看它做了什么都可以。我用strace命令跟踪执行了./test, 命令及输出如下:

$strace ./testexecve("./t", ["./t"], [/* 21 vars */]) = 0brk(NULL)                               = 0x2514000access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=38005, ...}) = 0mmap(NULL, 38005, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa9d1097000close(3)                                = 0access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)open("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \235\10\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0644, st_size=1566440, ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa9d1096000mmap(NULL, 3675136, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa9d0afa000mprotect(0x7fa9d0c6c000, 2097152, PROT_NONE) = 0mmap(0x7fa9d0e6c000, 49152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x172000) = 0x7fa9d0e6c000mmap(0x7fa9d0e78000, 13312, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa9d0e78000close(3)                                = 0access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)open("/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p*\0\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0644, st_size=89696, ...}) = 0mmap(NULL, 2185488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa9d08e4000mprotect(0x7fa9d08fa000, 2093056, PROT_NONE) = 0mmap(0x7fa9d0af9000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15000) = 0x7fa9d0af9000close(3)                                = 0access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\t\2\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa9d051a000mprotect(0x7fa9d06da000, 2097152, PROT_NONE) = 0mmap(0x7fa9d08da000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7fa9d08da000mmap(0x7fa9d08e0000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa9d08e0000close(3)                                = 0access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)open("/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0V\0\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0644, st_size=1088952, ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa9d1095000mmap(NULL, 3178744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa9d0211000mprotect(0x7fa9d0319000, 2093056, PROT_NONE) = 0mmap(0x7fa9d0518000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x107000) = 0x7fa9d0518000close(3)                                = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa9d1094000mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa9d1092000arch_prctl(ARCH_SET_FS, 0x7fa9d1092740) = 0mprotect(0x7fa9d08da000, 16384, PROT_READ) = 0mprotect(0x7fa9d0518000, 4096, PROT_READ) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa9d1091000mprotect(0x7fa9d0e6c000, 40960, PROT_READ) = 0mprotect(0x601000, 4096, PROT_READ)     = 0mprotect(0x7fa9d10a1000, 4096, PROT_READ) = 0munmap(0x7fa9d1097000, 38005)           = 0brk(NULL)                               = 0x2514000brk(0x2546000)                          = 0x2546000fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 6), ...}) = 0write(1, "open good\n", 10open good)             = 10exit_group(0)                           = ?+++ exited with 0 +++

大家认真看下,发现什么了没有?竟然没有open("abc.txt", ...)的系统调用,哇塞,这是什么鬼?到了这个时候,我不得不低下我骄傲的头颅,到c++ reference查文档去了,。看到cpp reference的open里边写着:

打开参考文档,我第一时间看了下openmode的解释(上图红框部分)。app(append的缩写),每次写之前先定位到stream的最后边,跟我记忆一样,没有问题。trunc(truncate的缩写),打开文件的时候丢弃现有文件里边的内容,跟我记忆中也一样,没毛病。然后看了下函数介绍,发现这个函数只是代理,后边继续调用了rdbuf()->open(filename, mode),然后我顺着介绍链接点开 std::basic_filebuf::open的文档,里边亮了!

看最后红框框住的这句话:如果openmode的组合不是上边列出来的之一,open()调用将失败!哇咔咔!然后再看,app 和 trunc 同时使用的情况并不在上边列表!(这里我要吐槽下,c++的文件流的错误处理做的真不够好,只提高了一个标志位告诉使用者成功还是失败,没有具体的错误码,这样让使用的人定位原因的时候很痛苦呀,捂脸。)

 

终于真相大白了,我们把std::ios:app去掉看看:

int main(){    ofstream f;    f.open("./abc.txt", ios::trunc);    if (f.bad()) {        cout << "open bad:" << strerror(errno) << endl;    } else {        cout << "open good" << endl;    }      f << "write something" << endl;    f.close();}

然后我们来编译并运行,输出还是一样:

$g++ -Wall -g -std=c++11 t.cpp -o t && ./topen good$ g++ -Wall -g -std=c++11 t.cpp -o t && ./topen good$ cat abc.txtwrite something

文件终于出来了!

我们再strace看下:

$ strace ./texecve("./t", ["./t"], [/* 21 vars */]) = 0brk(NULL)                               = 0xc0e000access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=38005, ...}) = 0mmap(NULL, 38005, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1c58c61000close(3)                                = 0access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)open("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \235\10\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0644, st_size=1566440, ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1c58c60000mmap(NULL, 3675136, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f1c586c4000mprotect(0x7f1c58836000, 2097152, PROT_NONE) = 0mmap(0x7f1c58a36000, 49152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x172000) = 0x7f1c58a36000mmap(0x7f1c58a42000, 13312, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f1c58a42000close(3)                                = 0access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)open("/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p*\0\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0644, st_size=89696, ...}) = 0mmap(NULL, 2185488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f1c584ae000mprotect(0x7f1c584c4000, 2093056, PROT_NONE) = 0mmap(0x7f1c586c3000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15000) = 0x7f1c586c3000close(3)                                = 0access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\t\2\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f1c580e4000mprotect(0x7f1c582a4000, 2097152, PROT_NONE) = 0mmap(0x7f1c584a4000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f1c584a4000mmap(0x7f1c584aa000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f1c584aa000close(3)                                = 0access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)open("/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0V\0\0\0\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0644, st_size=1088952, ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1c58c5f000mmap(NULL, 3178744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f1c57ddb000mprotect(0x7f1c57ee3000, 2093056, PROT_NONE) = 0mmap(0x7f1c580e2000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x107000) = 0x7f1c580e2000close(3)                                = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1c58c5e000mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1c58c5c000arch_prctl(ARCH_SET_FS, 0x7f1c58c5c740) = 0mprotect(0x7f1c584a4000, 16384, PROT_READ) = 0mprotect(0x7f1c580e2000, 4096, PROT_READ) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1c58c5b000mprotect(0x7f1c58a36000, 40960, PROT_READ) = 0mprotect(0x601000, 4096, PROT_READ)     = 0mprotect(0x7f1c58c6b000, 4096, PROT_READ) = 0munmap(0x7f1c58c61000, 38005)           = 0brk(NULL)                               = 0xc0e000brk(0xc40000)                           = 0xc40000open("./abc.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 6), ...}) = 0write(1, "open good\n", 10open good)             = 10write(3, "write something\n", 16)       = 16close(3)                                = 0exit_group(0)                           = ?+++ exited with 0 +++

open("./abc.txt", ...)系统调用终于有了!一个小时也过去了,捂脸。希望大家注意下,c++文件流的openmode,只有在上边表格列出来的组合才是允许的,像app 与 trunc 组合是不允许的,将导致打开文件失败。还有个疑问,为什么Windows下报ERROR_FILE_NOT_FOUND,Linux下却是没有错误?其实都没有到系统调用,获取到的错误码是之前的历史值,没有任何意义。

 

最后建议大家查cpp标准的时候使用cppreference.com,不要用cplusplus.com,cplusplus.com很久没有更新了,也不全,比如上边的openmode组合表格cplusplus.com那边就没有

 

更多最新文章,请扫描下边二维码,关注公众号:学习者说

公众号二维码

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐