【问题标题】:Why is this C function crashing at fclose?为什么这个 C 函数在 fclose 时崩溃?
【发布时间】:2010-10-26 12:31:07
【问题描述】:

请帮助我理解为什么这个函数在到达 fclose 时会抛出异常:


void receive_file(int socket, char *save_to, int file_size) {
    FILE *handle = fopen(save_to,"wb");
    if(handle != NULL) {
        int SIZE = 1024;
        char buffer[SIZE];
        memset(buffer,0,SIZE);
        int read_so_far = 0;
        int read_now = 0;
        int reading_unit = (file_size < SIZE) ? file_size : SIZE;
        do {
            read_now = read(socket,buffer,reading_unit);
            fwrite(buffer,read_now,1,handle);
            read_so_far += read_now;
            if(read_so_far >= file_size) {
                break;
            }
            memset(buffer, 0, sizeof(buffer));
        } while (1);
        read_now = 0;
        fclose(handle);
    }
    else {
        extern int errno;
        printf("error creating file");
        printf(" error code : %d",errno);
        exit(-1);
    }
}

Eclipse CDT 存在,但出现以下错误:

单步执行直到退出函数__kernel_vsyscall,
没有行号信息。

这个函数的目的是通过socket接收一个文件。

编辑:我使用的是 CentOS 5.3。问题是,文件被创建和写入。甚至 MD5 也是正确的。我不明白为什么它在 fclose 失败。

EDIT2:这是我设法获得的堆栈跟踪:

*** 检测到 glibc *** /home/linuser/workspace/proj/Debug/proj: free(): 下一个大小无效(正常):0x096a0068 *** ======= 回溯:========= /lib/libc.so.6[0x4fb0f1] /lib/libc.so.6(cfree+0x90)[0x4febc0] /lib/libc.so.6(fclose+0x136)[0x4e9c56] /home/linuser/workspace/proj/Debug/proj[0x8048cd8] /home/linuser/workspace/proj/Debug/proj[0x80492d6] /home/linuser/workspace/proj/Debug/proj[0x804963d] /lib/libc.so.6(__libc_start_main+0xdc)[0x4a7e8c] /home/linuser/workspace/proj/Debug/proj[0x8048901] ======= 内存映射:======== 001ff000-00200000 r-xp 001ff000 00:00 0 [vdso] 0046f000-00489000 r-xp 00000000 08:06 1280361 /lib/ld-2.5.so 00489000-0048a000 r-xp 00019000 08:06 1280361 /lib/ld-2.5.so 0048a000-0048b000 rwxp 0001a000 08:06 1280361 /lib/ld-2.5.so 00492000-005d0000 r-xp 00000000 08:06 1280362 /lib/libc-2.5.so 005d0000-005d2000 r-xp 0013e000 08:06 1280362 /lib/libc-2.5.so 005d2000-005d3000 rwxp 00140000 08:06 1280362 /lib/libc-2.5.so 005d3000-005d6000 rwxp 005d3000 00:00 0 005d8000-005fd000 r-xp 00000000 08:06 1280369 /lib/libm-2.5.so 005fd000-005fe000 r-xp 00024000 08:06 1280369 /lib/libm-2.5.so 005fe000-005ff000 rwxp 00025000 08:06 1280369 /lib/libm-2.5.so 009b2000-009bd000 r-xp 00000000 08:06 1280372 /lib/libgcc_s-4.1.2-20080825.so.1 009bd000-009be000 rwxp 0000a000 08:06 1280372 /lib/libgcc_s-4.1.2-20080825.so.1 009c5000-00aa5000 r-xp 00000000 08:06 5465873 /usr/lib/libstdc++.so.6.0.8 00aa5000-00aa9000 r-xp 000df000 08:06 5465873 /usr/lib/libstdc++.so.6.0.8 00aa9000-00aaa000 rwxp 000e3000 08:06 5465873 /usr/lib/libstdc++.so.6.0.8 00aaa000-00ab0000 rwxp 00aaa000 00:00 0 08048000-0804a000 r-xp 00000000 08:06 4884214 /home/linuser/workspace/proj/Debug/proj 0804a000-0804b000 rw-p 00001000 08:06 4884214 /home/linuser/workspace/proj/Debug/proj 096a0000-096c1000 rw-p 096a0000 00:00 0 [堆] b7e00000-b7e21000 rw-p b7e00000 00:00 0 b7e21000-b7f00000 ---p b7e21000 00:00 0 b7f99000-b7f9a000 rw-p b7f99000 00:00 0 b7faa000-b7fac000 rw-p b7faa000 00:00 0 bfa82000-bfa97000 rw-p bffea000 00:00 0 [堆栈]

这里是函数的“更新”版本:


void rec_file(int socket,char *path,int size)
{
    FILE *handle = fopen(path,"wb");
    char buffer[4096];
    int total_read = 0;
    if(handle != NULL)
    {
        while(1)
        {
            int bytes_read = recv(socket,buffer,4096,0);
            total_read    += bytes_read;
            if(bytes_read != -1)
            {
                fwrite(buffer,bytes_read,1,handle);
            }
            else
            {
                printf("read error ");
                exit(-1);
            }
            if(total_read >= size)
            {
                break;
            }
        }
        fclose(handle);
    }
    else
    {
        printf("error receiving file");
        exit(-1);
    }
}

也许这更干净?但是,我仍然收到相同的 fclose 异常。

编辑3: 我注释掉了所有内容,只留下了以下代码,遗憾的是,它仍然在 fclose 处引发异常:


void nrec(int sock,char* path,int size)
{
    FILE *handle = fopen(path,"wb");
    if(handle == NULL)
    {
        printf ("error opening file");
        return;
    }
    fclose(handle);
}

【问题讨论】:

  • 不要写'extern int errno;' -- 使用#include ;因为 'errno' 是一个可修改的整数 l 值,但不一定是 int。在多线程编程中,通常是#define errno *(errno_func()) where extern int *errno_func(void);
  • 你排除了save_to(你要写入的文件名)不是垃圾吗?还有,什么操作系统?
  • memset() 操作并不是真正需要的。
  • 另外,在上下文中使用 VLA 是不寻常且不必要的。您应该知道它是一个 C99 构造。
  • 还有一个:仅当 bytes_read 大于零时才进行写入。

标签: c exception function


【解决方案1】:

这段代码看起来很……不确定。大量不必要的分配,memset() 调用,复杂的逻辑。

限制您尝试阅读的数量是没有意义的。始终阅读尽可能多的缓冲区空间;如果可用数据越少,您得到的数据就越少。

它也不能正确处理错误。如果read() 失败,它可以返回-1,在这种情况下,Bad Things(TM) 将到处发生。

我的建议是清理它,考虑 I/O 函数的返回值并更好地检查它们,然后再试一次。

如果您在 Linux 中失败,请通过 Valgrind 运行它以获取跟踪崩溃的帮助。

【讨论】:

    【解决方案2】:

    不确定,但您知道read() 可以在错误时返回否定值,对吧?

    【讨论】:

      【解决方案3】:

      您没有检查是否从read() 调用中得到错误指示。如果您从 read() 得到 -1,然后将其未经检查地传递给 fwrite(),它以 size_t 作为其参数。当您的int 值转换为size_t(或被视为size_t)时,它会变成一个相当大的数字(4 GB - 在 32 位系统上为 1)。所以,fwrite() 会按照您的命令执行 - 尝试从不应该写入的位置写入大量数据。

      【讨论】:

        【解决方案4】:

        你需要测试read()的返回值——它可能会返回一个错误码,比如-1,以及读取的字节数。

        编辑:在您的新代码中,您应该测试 recv() 的返回值是否 >= 0,而不是它不是 -1。如果它返回零,你也应该打破。

        【讨论】:

        • 这不会发生。我在调试模式下运行代码。我从来没有收到 -1。
        • @Geo:还没有发生——将来可能会发生。假设所有系统调用都可以并且将在不方便的时候失败。例外: getpid() 不会失败 - 还有一些其他的。但是像 read() 这样定义了失败的调用将会失败,而且迟早会失败。
        【解决方案5】:
        • fclose() 返回值:如果文件关闭成功则返回 0,否则返回 EOF。考虑将您的代码修改为...

          if (fclose(handle)) { printf("错误关闭文件。");退出(-1); }

        • 正如其他人指出的,检查 read() 是否有错误。

        • 如果崩溃发生之后 fclose 返回 0(成功),并且所有 read() 都成功,那么问题可能出在其他地方。也许是VLA。考虑将缓冲区代码更改为静态文件或 malloc / free。

        • 如果调用者弄错了文件大小,比如说,声称文件是 500 字节,而实际上它只有 480 字节,你的代码会一直读取套接字吗?

        【讨论】:

        • 我没有机会执行此检查。该函数在达到 fclose 时会爆炸。
        • 进入fclose就炸了。 fclose 没有机会完成并返回。
        • 如果你注释掉除了 fopen 和 fclose 之外的所有内容,它还会崩溃吗?如果将读取单位更改为 (file_size : SIZE-10) 以便在最后有一些幸运字节,它还会崩溃吗?
        • 我评论了除 fopen 和 fclose 之外的所有内容,但它仍然崩溃。请查看新的编辑。
        • 我要感谢您非常有帮助的 cmets。我设法检测到错误,它不在此功能中。我在函数中“错误地”分配了一个缓冲区,而这一切都适得其反。非常感谢!
        【解决方案6】:

        可能是因为您在文件中写入的字节多于 file_size 吗?如果您将 file_size 设置为 1025,则使用当前逻辑,那么您最终将向文件写入 2048 个字节。我相信,您应该在 while 循环中重新计算 reading_unit。

        【讨论】:

          【解决方案7】:

          我不确定编译器将如何解释这两行

          int SIZE = 1024;
          char buffer[SIZE];
          

          您正在使用变量而不是常量来调整缓冲区大小。我不确定这是否会像您预期的那样运行。您可以尝试以下方法吗?

          #define SIZE (1024)
          char buffer[SIZE];
          

          【讨论】:

          • 编译器要么接受它(如果它处于 C99 模式),要么将其作为语法错误拒绝。
          • 编译器可能会接受它,然后用“退出时崩溃”来实现它。新功能,新的学习机会。
          【解决方案8】:

          edit3 中的代码看起来很合理。我怀疑您可能已经在其他地方踩到了一些内存,并且此时正在您的应用程序中遇到问题。

          您是否有边界检查器、净化器等可以运行以检查缓冲区溢出、使用空闲内存块等。

          【讨论】:

            【解决方案9】:

            您确定在使用此功能之前没有在其他地方损坏堆吗?尝试在 valgrind 下运行您的程序。

            【讨论】:

              【解决方案10】:

              如果您的“EDIT3”版本的代码仍然崩溃,那么问题出在代码的其他地方。如果您在程序的另一部分中犯了错误(例如,两次释放某些内容,取消引用未指向您认为的位置的指针),那么导致的损坏可能直到以后才被发现(例如当您到达fclose())。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-07-18
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多