sprintf() 期间的段错误

Segfault during a sprintf()

因此,我目前正在为我的 Unix OS class 进行系统编程。该程序应该做的就是读取二进制文件并将行输出到 CSV 文件。我觉得我快完成了,但出于某种原因,我一直遇到段错误。

澄清一下: fd1 = 输入文件, fd2 = 输出文件, numrecs = 来自输入文件的记录数。 main() 中的某处:

for(i=0;i<numrecs;i++){
    if((bin2csv(fd1, fd2)) == -1){
        printf("Error converting data.\n");
    }
}

int bin2csv(fd1, fd2){
    bin_record rec;
    char buffer[100];
    int buflen;
    strncpy(buffer,"[=10=]", 100); /* fill buffer with NULL */
    recs = &rec;

    /* read in a record */
    if((buflen = read(fd1, &recs, sizeof(recs))) < 0){
        printf("Fatal Error: Data could not be read.\n");        
        return -1;
    }

   sprintf(buffer, "%d, %s, %s, %f, %d\n", recs->id, recs->lname, recs->fname, recs->gpa, recs->iq);
   printf("%s\n", buffer);
   write(fd2, buffer, sizeof(buffer));
   return 0;
}

段错误发生在 "sprintf(buffer, etc..);" 行上,但是,我无法弄清楚为什么会这样。

这是 gdb 吐出的错误:

Program received signal SIGSEGV, Segmentation fault.
0x0000000100000c87 in bin2csv (fd1=3, fd2=4) at bin2csv.c:25
25 sprintf(buffer, "%d, %s, %s, %f, %d\n", recs->id, recs->lname,
recs->fname, recs->gpa, recs->iq);

希望这些信息足够了。谢谢!

看起来recs是一个指针。您正在将字节直接读入该指针,就像从文件中读取原始内存地址:

read(fd1, &recs, sizeof(recs))

然后您开始在对 sprintf 的调用中使用它... BOOM!

实际上根本没有理由使用它(它是全局的吗?)...即使您通过 recs = &rec 初始化它,并且假设您不丢弃它,它仍然不会包含该函数之外的有效地址。那是因为 rec 是局部变量。

所以,直接读入 rec 就像这样:

read(fd1, &rec, sizeof(rec))

然后在 sprintf 行中,使用 rec.id 而不是 recs->id (etc).

我在这里看到了一些问题:

  1. sprintf 不会阻止写入超过字符串缓冲区的末尾。事实上,它不知道该缓冲区的长度(在您的情况下为 100 字节)。由于您已经在堆栈中设置了缓冲区,如果 sprintf 过度运行您的缓冲区(它可以使用长名字或姓氏或垃圾字符串作为输入),您的堆栈将被破坏并且可能出现段错误。您可能需要考虑包含逻辑以确保 sprintf 不会超过您拥有的缓冲区数量 space。或者更好的是完全避免 sprintf(更多内容见下文)

  2. 您没有在提供的代码中处理文件结尾。对于文件结尾,读取 returns 0。如果将错误的指针传递给 sprintf,它将失败。

  3. 您使用的函数是使用小整数作为文件描述符的 UNIX 派生函数(POSIX 的一部分,但级别很低)。我建议改用基于 FILE * 的。感兴趣的 I/O 函数是 fopenfclosefprintffwrite 等。这样就无需使用 sprintf

有关详细信息,请参阅 this previous question

if((buflen = read(fd1, &recs, sizeof(recs))) < 0){

使用 <= 0 而不是 < 0,否则当 return 值为 0 时,sprintf(buffer ... 可能会在尝试取消引用 recs->id 有一个未初始化的值。

您遇到了一些问题: 1) bin_record 的结构。它有 char[] 并且有可能溢出。 2) 在 sprintf 中你不能设置缓冲区最大大小。最好像这样使用 snprintf:

 sprintf(buffer, 100, "%d, %s, %s, %f, %d\n", recs->id, recs->lname, recs->fname, recs->gpa, recs->iq);

3) 用 null 填充缓冲区:

memset (buffer,'[=11=]',100);