C二进制文件无法正常工作

时间:2016-02-29 16:22:37

标签: c struct

我有一个问题。这是一个家庭作业,并没有理解它。这个简单的程序要求用户将他/她的姓名和电话号码写入文件,如果它找到RDS电话号码ex({{ 1}})然后写出二进制文件的所有者和电话号码。 问题是程序没有将任何内容写入二进制文件(output.dat)。

到目前为止,我做到了这一点:

0*3*12415324

1 个答案:

答案 0 :(得分:0)

当我运行你的代码时,我得到一个段错误。正在运行valgrind会显示问题。

==75112== Invalid read of size 8
==75112==    at 0x1001D0C5F: flockfile (in /usr/lib/system/libsystem_c.dylib)
==75112==    by 0x1001D303C: fscanf (in /usr/lib/system/libsystem_c.dylib)
==75112==    by 0x100000D42: main (test.c:21)
==75112==  Address 0x68 is not stack'd, malloc'd or (recently) free'd
==75112== 
==75112== 
==75112== Process terminating with default action of signal 11 (SIGSEGV)
==75112==  Access not within mapped region at address 0x68
==75112==    at 0x1001D0C5F: flockfile (in /usr/lib/system/libsystem_c.dylib)
==75112==    by 0x1001D303C: fscanf (in /usr/lib/system/libsystem_c.dylib)
==75112==    by 0x100000D42: main (test.c:21)
由于fscanf失败,

fopen被传递了伪造的文件句柄。在这种情况下,我没有input.txt。始终检查fopen的结果。我使用包装函数来确保始终发生这种情况。

#include <errno.h>
#include <string.h>

FILE *open_file(const char *filename, const char *mode) {
    FILE *fp = fopen(filename, mode);
    if( fp == NULL ) {
        fprintf(stderr, "Could not open %s: %s.\n", filename, strerror(errno));
        exit(errno);
    }

    return fp;
}

当我用fopen替换open_file时,我收到了一条很好的错误消息。

$ ./test
Could not open input.txt: No such file or directory.

好的,touch input.txt再次运行。另一个段错误。 Valgrind再次......

==75478== Conditional jump or move depends on uninitialised value(s)
==75478==    at 0x100000D0F: main (test.c:32)
==75478== 
==75478== Conditional jump or move depends on uninitialised value(s)
==75478==    at 0x100000D91: main (test.c:36)
==75478== 
==75478== Conditional jump or move depends on uninitialised value(s)
==75478==    at 0x100000DB9: main (test.c:37)

前两个约为n,未初始化,因为input.txt为空。应该填充它的fscanf失败,因此n包含它开始的任何垃圾。循环基于n中的垃圾运行随机次数。它尝试并且失败,从input.txt读取,因此p未被初始化并且还包含垃圾。

第三个错误是关于尝试在p中读取垃圾箱。 n包含垃圾,因此for循环会尝试从未初始化的p数组中读取。

这可以通过初始化n来解决,并像以前一样检查fscanf是否有效。同样,包装函数可确保检查错误。

int scan_check(FILE *restrict stream, const char *restrict format, ...) {
    va_list args;
    va_start(args, format);
    int ret = vfscanf(stream, format, args);

    if( ret <= 0 ) {
        fprintf(stderr, "Could not match input to '%s'.\n", format);
        exit(errno);
    }

    return ret;
}

现在它运行到最后。一切都好,对吗?不,valgrind再说一遍。

==76285== Syscall param write(buf) points to uninitialised byte(s)
==76285==    at 0x1002DE97A: write$NOCANCEL (in /usr/lib/system/libsystem_kernel.dylib)
==76285==    by 0x1001D8844: _swrite (in /usr/lib/system/libsystem_c.dylib)
==76285==    by 0x1001D12FE: __sflush (in /usr/lib/system/libsystem_c.dylib)
==76285==    by 0x1001D1001: fclose (in /usr/lib/system/libsystem_c.dylib)
==76285==    by 0x100000E6E: main (test.c:60)
==76285==  Address 0x100806bc4 is 4 bytes inside a block of size 4,096 alloc'd
==76285==    at 0x100008EBB: malloc (vg_replace_malloc.c:303)
==76285==    by 0x1001D468E: __smakebuf (in /usr/lib/system/libsystem_c.dylib)
==76285==    by 0x1001E91DF: __swsetup (in /usr/lib/system/libsystem_c.dylib)
==76285==    by 0x1001D3928: __sfvwrite (in /usr/lib/system/libsystem_c.dylib)
==76285==    by 0x1001D3F02: fwrite (in /usr/lib/system/libsystem_c.dylib)
==76285==    by 0x100000DDB: main (test.c:54)

这告诉我们fwrite来电有问题。

fwrite(p[i].name,sizeof(PERSON),n,fout);
fwrite(p[i].tel,sizeof(PERSON),n,fout);

这就是写nPERSON结构大小的项目。如果您正在编写所有p,那就没问题了。但是你只写了一个名字和一个电话号码。相反,你想要写出等于字符串长度的字符,再加上一个用于空字节的字符。

fwrite(p[i].name, sizeof(char), strlen(p[i].name)+1, fout);
fwrite(p[i].tel,  sizeof(char), strlen(p[i].tel)+1 , fout);

最后,把它们放在一起,valgrind没有抱怨。

int main()
{
    int n = 0;
    PERSON p[50];

    FILE *fin = open_file("input.txt","rt");
    FILE *fout = open_file("output.dat","wb");

    scan_check(fin,"%i", &n);

    for (int i=0; i<n; ++i)
    {
        scan_check(fin,"%s%s", p[i].name, p[i].tel);
    }

    for (int i=0; i<n; ++i){
        if (p[i].tel[1]=='3'){
            fwrite(p[i].name, sizeof(char), strlen(p[i].name)+1, fout);
            fwrite(p[i].tel,  sizeof(char), strlen(p[i].tel)+1 , fout);
        }
    }

    fclose(fin);
    fclose(fout);

    return 0;
}

通过消除固定大小的p数组,可以使此代码更安全。没有必要。如果input.txt中的人数超过50人,则列表将会溢出,而不是存储人员列表以便稍后输出,而是在单个for循环中输出它们。

PERSON p;

....

for (int i=0; i<n; ++i)
{
    scan_check(fin,"%s%s", p.name, p.tel);

    if (p.tel[1]=='3'){
        fwrite(p.name, sizeof(char), strlen(p.name)+1, fout);
        fwrite(p.tel,  sizeof(char), strlen(p.tel)+1 , fout);
    }
}
同样不需要{p> ninput.txt无需告诉您有多少条记录,而是在eof(fin)之前阅读该文件。这可以避免n不准确。