read()失败,地址错误,valgrind显示Syscall参数读取(buf)指向无法寻址的字节(s)

我有一个函数来读取一个文件,使用read()系统调用,并返回一个char指针,从文件中读取数据。 如果需要,函数会重新分配空间。 读取失败后,出现错误“Bad Address”。 失败的最小代码如下所示:

 #include <errno.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> const unsigned BUFSIZE = 8192; typedef struct { char* buffer; long size; } string_t; string_t read_file(const char* path) { string_t error = { .buffer = NULL, .size = -1 }; int fd = open(path, O_RDONLY); if (fd == -1) { perror("open() failed in read_file"); return error; } string_t s; s.buffer = malloc(BUFSIZE * sizeof(char)); s.size = 0; int nread = 0; long total_read = 0; while ((nread = read(fd, s.buffer + total_read, BUFSIZE)) != 0) { if (nread == -1) { if (errno == EINTR) { perror("error EINTR"); continue; } else { perror("read() failed in read_file"); close(fd); return error; } } else { printf("%ld %ld %d\n", total_read, s.size, nread); total_read += nread; s.size = total_read; if (nread == BUFSIZE) { if (realloc(s.buffer, s.size + BUFSIZE) == NULL) { perror("out of memory..."); close(fd); return error; } } } } close(fd); s.buffer[s.size] = 0; return s; } int main() { const char* path = "/usr/share/dict/cracklib-small"; string_t s = read_file(path); if (s.size == -1) { printf("error\n"); return 1; } printf("%s\n", s.buffer); free(s.buffer); return 0; } 

运行这个提供了以下内容:

 0 0 8192 8192 8192 8192 16384 16384 8192 24576 24576 8192 32768 32768 8192 40960 40960 8192 49152 49152 8192 57344 57344 8192 65536 65536 8192 73728 73728 8192 81920 81920 8192 90112 90112 8192 98304 98304 8192 106496 106496 8192 114688 114688 8192 122880 122880 8192 131072 131072 8192 read() failed in read_file: Bad address error 

Valgrind表示:

 ==4299== Syscall param read(buf) points to unaddressable byte(s) ==4299== at 0x5184F00: __read_nocancel (in /usr/lib/libc-2.21.so) ==4299== by 0x400A58: read_file (file_helpers.c:31) ==4299== by 0x400AA3: main (file_helpers.c:64) ==4299== Address 0x7568040 is 0 bytes after a block of size 8,192 free'd ==4299== at 0x4C2C29E: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==4299== by 0x400A12: read_file (file_helpers.c:46) ==4299== by 0x400AA3: main (file_helpers.c:64) ==4299== 

我可以看到它抱怨realloc,但我不明白是什么导致错误的地址错误。 read()在最后一次realloc()之后写入的缓冲区是否被损坏?

wc -c报告运行该函数的文件有477238个字节。

您对realloc()看起来一见钟情。

 if (realloc(s.buffer, s.size + BUFSIZE) == NULL) 

这个语句检查realloc()是否成功。 如果失败,则处理这种情况。 精细。

如果realloc()成功呢?

根据手册页 ,

realloc()函数返回一个指向新分配的内存的指针,该内存适合于任何类型的变量,并且可能与ptr不同 ,如果请求失败,则返回NULL。 如果size等于0 ,则返回NULL或适合传递给free()的指针。 如果realloc()失败,则保留原始块; 它不被释放或移动。

这意味着,你正在失去新分配的内存,然后使用free() d内存。

我想,你已经预料到realloc()会调整s.buffer本身的大小,但是恐怕不是这样的。

解答

您应该在临时变量中收集realloc()的返回值,检查NULL ,如果不是NULL ,则将相同的指针分配给指针s.buffer

FWIW, 不要使用原来的指针本身来收集realloc()的返回值,万一失败,也会丢失实际的内存。

这个问题可能与你如何使用reallocrealloc(s.buffer, s.size + BUFSIZE) == NULL不是使用realloc的正确方法。 从这里取得的realloc的返回值是:

在大小不等于0的情况下成功完成后,realloc()将返回一个指向( 可能移动的 )已分配空间的指针。 如果size是0,则可以返回一个空指针或一个可以成功传递给free()的唯一指针。 如果没有足够的可用内存,realloc()将返回一个空指针并将errno设置为ENOMEM。

关键部分是realloc可以移动分配的空间及其数据的事实。 所以你不能只检查返回值是否为NULL。 你想要做更多的事情:

 void *tmp = realloc(s.buffer, s.size + BUFSIZE); if (tmp == NULL) { perror("out of memory..."); free(s.buffer); // Release the memory to avoid a leak close(fd); return error; } s.buffer = tmp; 

换句话说,用realloc的返回值更新指针。 如果数据没有移动, realloc返回传给它的内存; 如果它被移动, realloc将返回一个新的地址。

更新:

您可能没有遇到的另一个问题是如何处理read的返回值。 如果read返回小于请求比你不会重新realloc更多的内存,进一步的读取可能会读取缓冲区。 您现在可能不会遇到这种情况,因为只有read不会失败,读取的数据也不会少于所请求的数据量。 包含realloc修复的解决方案如下:

 int nread = 0; long total_read = 0; int space_remaining = BUFSIZE; while ((nread = read(fd, s.buffer + total_read, space_remaining)) != 0) { if (nread == -1) { if (errno == EINTR) { perror("error EINTR"); continue; } else { perror("read() failed in read_file"); close(fd); return error; } } else { printf("%ld %ld %d\n", total_read, s.size, nread); total_read += nread; s.size = total_read; space_remaining -= nread; if (space_remaining == 0) { void *tmp = realloc(s.buffer, s.size + BUFSIZE); if (tmp == NULL) { perror("out of memory..."); free(s.buffer); // Release the memory to avoid a leak close(fd); return error; } s.buffer = tmp; space_remaining = BUFSIZE; } } } 

变量space_remaining用于跟踪缓冲区剩余多少空间。 这是读取的数量,并在缓冲区的大小增加时重置。 由于您正在重新分配更多空间,因此您不希望执行先前建议的典型(BUFSIZE-total_read)模式,尽管这是您所看到的典型模式。

如果read总是返回请求的数据量,那么您将再也看不到这个问题。