两次fclose引发的血案

代码本来在Windows上开发的,功能基本完毕迁移到Linux上,结果一跑,乱象重重。这里只列出两个。
 
一崩溃:
 /mnt/diskc/db/app/bin/mysqld: double free or corruption (out): 0x00007f09cc00c900 ***
======= Backtrace: =========
/lib64/libc.so.6[0x31434760e6]
/lib64/libc.so.6[0x3143478c13]
/lib64/libc.so.6(fclose+0x14d)[0x314346674d]
/mnt/diskc/db/app/bin/mysqld(……内部代码……)[0x781d59]

……

/lib64/libpthread.so.0[0x3143c07851]
/lib64/libc.so.6(clone+0x6d)[0x31434e890d]

 

挂死:

查询一张表时正常返回结果,然后正常shutdown关闭程序,就挂住了。用pstack可以打出调用栈来。
Thread 2 (Thread 0x7f8a6c074700 (LWP 1506)):
#0  0x00000031434f806e in __lll_lock_wait_private () from /lib64/libc.so.6
#1  0x0000003143466815 in _L_lock_36 () from /lib64/libc.so.6
#2  0x0000003143466652 in fclose@@GLIBC_2.2.5 () from /lib64/libc.so.6
#3  0x00000000007828f6 in …………内部代码………… ()
#5  0x00000000005e9e7d in closefrm(TABLE*, bool) ()
#6  0x0000000000543122 in free_cache_entry(TABLE*) ()
#7  0x000000000054be7c in close_cached_tables(THD*, TABLE_LIST*, bool, unsigned long) ()
#8  0x000000000054c1a4 in table_def_start_shutdown() ()
#9  0x0000000000512f6c in clean_up(bool) ()
#10 0x00000000005131ee in unireg_end() ()
#11 0x0000000000514b82 in kill_server ()
#12 0x0000000000514c3e in kill_server_thread ()
#13 0x0000003143c07851 in start_thread () from /lib64/libpthread.so.0
#14 0x00000031434e890d in clone () from /lib64/libc.so.6

 

一头乱麻,索性就从最容易重现的第二个挂死现象查起了。
谷歌一搜 fclose  __lll_lock_wait_private  ,神啊,多是说程序clone之后再fclose有过这样的问题,有的还提供了对glibc的patch,确认了一下,我们的glibc库已经很新了。而且我们的程序虽然有多线程,也对一个文件打开过两次,但这两次操作是在一个线程内部的,所以跟网上的信息不太相符。
 
后来自己写了一个测试程序,在一个程序内两次fopen,再依次关闭,也都正常。又改成主线程里开一次,子线程里开一次,再关,仍都正常。
 
最后没办法仔细查代码,发现关于文件打开我们的流程是:
 
OpenFile(FILE* fp)
{
   if (fp != NULL)
    fclose(fp);
 
   fopen(fp,,,,,);
}

而在某些处理后需要关闭时,操作为:

fclose(fp);

 

虽然不太确定,但做事总要干净些,于是把关闭操作改为:
fclose(fp);
fp = NULL;

 

搞完了,一切正常了。说明我们有良好的编码习惯是多么重要。另外出Bug时还是要多查自己的代码,基础库的问题没那么容易被我们撞上的。
 
查查man fclose,说了两次关闭的结果然是不确定的。
RETURN VALUE
       Upon successful completion 0 is returned.  Otherwise, EOF is returned and errno is 
set to indicate the error. In either case any further access (including
another call to fclose()) to the stream results in undefined behavior.

 

再附上我自己的测试多次fopen的代码。
#include <stdio.h>
#include <pthread.h>

char szPath[312] = {0};
char readbuf[312] = {0};

void*  thread_func(void* p)
{
        printf("\n\n....hello from son thread %d\n", pthread_self());
        FILE *fp_r = NULL;
        fp_r = fopen(szPath, "rb+"); 
        printf("fp_r=%d after fopen rb+ \n", fp_r);

        if (fp_r == NULL)
                return NULL;

        if (fread(readbuf, 10, 1, fp_r) < 0 )
        {
                printf("read fp_r error!!\n");
                return NULL;
        }
        printf("read fp_r ok!!\n");
        sleep(10);

        fclose(fp_r);
        printf("close fp_r ok!!\n");

        pthread_exit(NULL);
}

int main()
{
        FILE *fp_w = NULL;

        pthread_t t_id ;

        sprintf(szPath, "/disk1/tmp");

        fp_w = fopen(szPath, "wb+"); 
        printf("fp_w=%d after fopen wb+ \n", fp_w);

        if (fp_w == NULL)
        {
                return 0;
        }

        if (fwrite(szPath, sizeof(szPath), 1, fp_w) != 1) 
        { 
                return -1; 
        } 

        fclose(fp_w); 
        printf("fp_w=%d after fclose wb+ \n", fp_w);
        fp_w = NULL;

        fp_w = fopen(szPath, "rb+"); 
        printf("fp_w=%d after fopen rb+ \n", fp_w);

        if (fp_w == NULL)
                return -1;

        if (fread(readbuf, 10, 1, fp_w) < 0)
        {
                printf("read fp_w error!!\n");
                return -1;
        }
        printf("read fp_w ok!!\n");

        if (pthread_create(&t_id, NULL, &thread_func, (void*)&t_id) != 0)
        {
                printf("  pthread_create fail!! \n");
                return -1;
        }
        printf("create thread done.\n");
        sleep(3);

        fclose(fp_w);
        printf("close fp_w ok!!\n");
        pthread_join(t_id,NULL);
        printf("-----------------------\n");


        return 0;
}

编译命令为:gcc tfclose.c -lpthread

转载于:https://www.cnblogs.com/szlx/p/fclose_twice.html

### 如何在C编程中使用 `fclose` 函数以及常见的问题 #### 使用 `fclose` 的基本方法 `fclose` 是 C 标准库中的函数,用于关闭之前打开的文件流。当程序完成对某个文件的操作时,调用此函数可以释放与该文件关联的资源并保存数据到磁盘上。如果未正确关闭文件,则可能导致内存泄漏或其他不可预测的行为。 以下是 `fclose` 的标准定义及其返回值说明: - **原型**: `int fclose(FILE *stream);` - **参数**: 接收一个指向已打开文件的指针作为输入。 - **返回值**: 成功时返回 0;失败则返回 EOF[^1]。 下面展示了一个简单的例子来演示如何利用 lambda 表达式配合智能指针自动管理文件描述符从而避免手动调用 `fclose`: ```cpp auto close_file = [](FILE* file) { fclose(file); }; std::unique_ptr<FILE, decltype(close_file)> file(fopen("example.txt", "r"), close_file); ``` 上述代码片段通过创建自定义删除器实现了 RAII(Resource Acquisition Is Initialization)模式下的文件操作安全机制。 #### 常见错误及解决方案 尽管 `fclose` 功能强大且简单易用,但在实际开发过程中仍可能出现一些典型失误,比如尝试关闭已经关闭或者从未开启过的文件句柄等情况都会引发未定义行为。因此,在编写涉及文件处理的应用程序时需格外小心注意以下几点事项: 1. **重复关闭同一文件** 如果同一个 FILE 对象被多传递给 `fclose`, 将会产生不确定的结果甚至崩溃应用程序。所以应该确保每只执行一有效的关闭动作即可. 2. **忽略其返回状态码** 开发者往往忽视检查 `fclose` 是否成功结束这一环节。然而实际上它确实有可能因为某些原因而未能顺利完成任务(例如缓冲区写入失败),故建议始终验证它的输出结果以便及时发现潜在隐患: ```c if (fclose(fp) != 0){ perror("Error closing file"); } ``` 3. **异常情况下的资源回收** 当存在可能抛出异常的情形下读取/修改外部存储介质上的资料时,单纯依赖显式的 `fclose` 调用不足以保障所有场景都能妥善处置所占用的系统级设施。此时可考虑采用高级别的抽象工具诸如 C++ 中的标准模板库组件——`std::fstream` 或者借助第三方框架实现更稳健的设计思路。 --- ### 示例代码:显示如何优雅地运用 `fclose` 这里给出一段完整的示范程序,用来阐述怎样恰当地应用 `fclose` 来终止文件访问周期的同时兼顾鲁棒性和效率考量因素: ```c #include <stdio.h> #include <stdlib.h> void processFile(const char* filename){ FILE *fp; /* 打开指定路径名对应的实体 */ fp = fopen(filename,"w+"); if (!fp){ fprintf(stderr,"%s cannot be opened.\n",filename); exit(EXIT_FAILURE); } // 进行必要的业务逻辑运算... fputs("Sample content written here.",fp); /* 关闭前先刷新内部缓存至目标位置 */ fflush(fp); /* 验证最终能否正常断连连接关系 */ if (fclose(fp)!=0){ perror("Failure occurred during termination phase."); } } int main(){ const char* testFileName="testfile.dat"; processFile(testFileName); return EXIT_SUCCESS; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值