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).
我在这里看到了一些问题:
sprintf
不会阻止写入超过字符串缓冲区的末尾。事实上,它不知道该缓冲区的长度(在您的情况下为 100 字节)。由于您已经在堆栈中设置了缓冲区,如果 sprintf
过度运行您的缓冲区(它可以使用长名字或姓氏或垃圾字符串作为输入),您的堆栈将被破坏并且可能出现段错误。您可能需要考虑包含逻辑以确保 sprintf 不会超过您拥有的缓冲区数量 space。或者更好的是完全避免 sprintf
(更多内容见下文)
您没有在提供的代码中处理文件结尾。对于文件结尾,读取 returns 0。如果将错误的指针传递给 sprintf
,它将失败。
您使用的函数是使用小整数作为文件描述符的 UNIX 派生函数(POSIX 的一部分,但级别很低)。我建议改用基于 FILE *
的。感兴趣的 I/O 函数是 fopen
、fclose
、fprintf
、fwrite
等。这样就无需使用 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);
因此,我目前正在为我的 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).
我在这里看到了一些问题:
sprintf
不会阻止写入超过字符串缓冲区的末尾。事实上,它不知道该缓冲区的长度(在您的情况下为 100 字节)。由于您已经在堆栈中设置了缓冲区,如果sprintf
过度运行您的缓冲区(它可以使用长名字或姓氏或垃圾字符串作为输入),您的堆栈将被破坏并且可能出现段错误。您可能需要考虑包含逻辑以确保 sprintf 不会超过您拥有的缓冲区数量 space。或者更好的是完全避免sprintf
(更多内容见下文)您没有在提供的代码中处理文件结尾。对于文件结尾,读取 returns 0。如果将错误的指针传递给
sprintf
,它将失败。您使用的函数是使用小整数作为文件描述符的 UNIX 派生函数(POSIX 的一部分,但级别很低)。我建议改用基于
FILE *
的。感兴趣的 I/O 函数是fopen
、fclose
、fprintf
、fwrite
等。这样就无需使用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);