打开超大文件时使用open命令出现段错误

3

我正在学校上网络课程,第一次使用C/GDB。我们的任务是制作一个与客户端浏览器通信的Web服务器。我已经很顺利地进行了,并且可以打开文件并将它们发送到客户端。一切都很顺利,直到我打开一个非常大的文件,然后程序就崩溃了。由于我不是C/GDB专家,所以如果这让我问一些愚蠢的问题并且不能自己找到解决方案,我感到很抱歉,但是当我查看核心转储时,我发现我的程序崩溃在这里:

if (-1 == (openfd = open(path, O_RDONLY)))

具体而言,我们的任务是打开文件并将其发送到客户端浏览器。我的算法如下:
  1. 打开/错误捕获
  2. 将文件读入缓冲区/错误捕获
  3. 发送文件
我们还负责确保在发送非常大的文件时服务器不会崩溃。但我的问题似乎在于打开它们。我可以很好地发送所有较小的文件。有问题的文件大小为29.5MB。
整个算法如下:
ssize_t send_file(int conn, char *path, int len, int blksize, char *mime) {
  int openfd; // File descriptor for file we open at path
  int temp; // Counter for the size of the file that we send
  char buffer[len]; // Buffer to read the file we are opening that is len big

  // Open the file
  if (-1 == (openfd = open(path, O_RDONLY))) {
    send_head(conn, "", 400, strlen(ERROR_400));
    (void) send(conn, ERROR_400, strlen(ERROR_400), 0);
    logwrite(stdout, CANT_OPEN);
    return -1;
  }

  // Read from file
  if (-1 == read(openfd, buffer, len)) {
    send_head(conn, "", 400, strlen(ERROR_400));
    (void) send(conn, ERROR_400, strlen(ERROR_400), 0);
    logwrite(stdout, CANT_OPEN);
    return -1;
  }
  (void) close(openfd);

  // Send the buffer now
  logwrite(stdout, SUC_REQ);
  send_head(conn, mime, 200, len);      
  send(conn, &buffer[0], len, 0);
  return len;
}

我不知道这是否只是因为我是Unix/C的新手。如果是,很抱歉。但是您的帮助将不胜感激。


展示一些相关代码。更好的做法是编写一个最小化程序来展示这个问题。 - Ignacio Vazquez-Abrams
2
只要“path”是指向可读内存中以NUL结尾的字符串的第一个字符的指针,我无法想象在该行会出现段错误。 (而在许多情况下,“open”更可能返回-1并设置“errno = EFAULT”。)你确定那是错误的那一行吗? - ephemient
可能不是这样的。我还不太会使用GDB或Unix环境。但是当我在gdb webserver webserver.core中键入此命令时,它会显示“程序因信号11终止,即分段错误”。问题出在if (-1 == (openfd = open(path, O_RDONLY))这一行代码上。我猜这就是原因。 - Chris
2
哦,亲爱的。char buffer[len]--请不要在栈上分配巨大的数组(或巨大的任何东西)! - ephemient
4个回答

4

我可能误解了你在问题中的意思,但我认为需要指出的是,通常尝试一次性读取整个文件是不明智的,因为你可能会处理一些超出内存承载能力的内容。

更聪明的做法是分配一个特定大小的缓冲区,比如8192字节(反正我经常这么做),然后只需始终读取和发送那么多数据,直到read()操作返回0(且没有设置errno)表示流结束。


还不错,但我倾向于使用10240。 - Richard Pennington
3
好的。我只是喜欢二的幂次方,我想。 - Platinum Azure
@Richard:为什么是10240?那不是2的幂次方。 - C. K. Young
@Platinum Azure:你并不孤单。我认识的许多程序员也发现使用2的幂更加符合逻辑。 - C. K. Young
我会修改这个,因为你提出了一个很好的观点。我会等到解决我的开放性问题之后再进行修改,因为我已经让所有小文件都能正常工作了。我猜如果一次性发送整个巨大文件会出现问题,所以我也需要解决这个问题。 - Chris
显示剩余3条评论

4
我怀疑你遇到了stackoverflow(我在这个网站上使用这个术语应该会得到额外的积分)。
问题是你一次性在堆栈上为整个文件分配缓冲区。对于较大的文件,这个缓冲区比堆栈还要大,下一次尝试调用函数(因此将一些参数放在堆栈上)时,程序就会崩溃。
在打开行出现崩溃是因为在堆栈上分配缓冲区实际上并没有写入任何内存,它只是改变了堆栈指针。当你调用open尝试将参数写入堆栈时,堆栈顶部已经溢出,这导致了崩溃。
解决方案如Platinum Azure或dreamlax所建议的那样,逐个读取文件的小部分或者使用malloc或new在堆上分配缓冲区。

除非您使用C99的VLAs或alloca,否则分配可变数量的堆栈空间实际上并不容易。我喜欢这个假设....哦。我看到OP已经编辑了他们的问题以包括代码,并且他们正在使用VLAs。糟糕的OP!而+1给你。 - ephemient

3

与其使用可变长度数组,不如尝试使用malloc分配内存。

char *buffer = malloc (len);

...

free (buffer);

我刚刚在我的系统上进行了一些简单的测试,当我使用大尺寸的可变长度数组(就像你遇到问题的那个大小)时,我也会遇到SEGFAULT错误。


我应该在什么时候决定使用malloc分配空间而不是使用可变长度数组?如果我确实使用可变长度数组,那么我可以将其最大化到多大?也许这有点含糊不清,但这似乎是我在这里遇到的问题的一部分。 - Chris
1
可变长度数组通常通过在堆栈上腾出空间来实现,程序的堆栈与从 malloc 获得的内存相比通常非常少。您可以创建的最大 VLA 取决于操作系统为您的进程提供的堆栈空间大小。通常应保持可变长度数组非常小,4 千字节通常是我所能到达的极限,即使如此,我仍担心我要求太多了,尽管有些系统具有相当大的堆栈,约为 4 MB 或有时甚至为 8 MB,但这取决于系统。 - dreamlax
个人而言,我不喜欢使用在堆栈上分配的可变长度数组。我的意思是,它依赖于系统,这意味着它可能不是确定性的,至少在不同的系统上是如此。使用堆和malloc,我可以更有信心地得到我想要的数量,或者如果系统此时内存不足,则会耗尽内存。顺便说一句,在堆中只能获得一定量的内存,因此,如果您正在处理一个真正大的文件(32位系统上> 4GB),如果您尝试使用此方法,计算机将拒绝服务,因此需要使用缓冲区解决方案。 - Platinum Azure

2
你正在堆栈上分配缓冲区,而且它太大了。
当你在堆栈上分配存储空间时,编译器所做的就是减少堆栈指针以腾出足够的空间(这将保持堆栈变量分配为常数时间)。 它不会尝试触及任何这些堆叠的内存。 然后,当你调用open()时,它会尝试将参数放在堆栈上并发现已溢出堆栈并崩溃。
你需要按块操作文件,对其进行内存映射(mmap())或malloc()存储。此外,path应该声明为const char*。

一个滑动的mmap窗口需要一些努力,但可以非常棒 :) (为什么不映射整个文件?嗯,有时您只有2GB的虚拟地址空间,而您想处理一个8GB的文件...) - ephemient
是的,但克里斯说是23.5 MB,所以我建议采用最简单的解决方案。 - Mike DeSimone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接