read()失败,地址错误,valgrind显示Syscall param read(buf)指向不可寻址的字节

时间:2015-07-03 16:11:15

标签: c linux realloc

我有一个使用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个字节。

2 个答案:

答案 0 :(得分:4)

您对realloc()的使用一见不法。

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

此语句检查realloc()是否成功。如果失败,这将处理案件。细

如果realloc()成功了怎么办?

根据man page

  

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

这意味着,您将丢失新分配的内存,然后使用free()内存。

我想,您已经预料realloc()会调整s.buffer本身的大小,但是,我担心,这不是这种情况。

<强>解决方案

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

FWIW,使用原始指针本身来收集realloc()的返回值,如果失败,你也会丢失实际内存。

答案 1 :(得分:4)

问题可能出在您使用realloc的方式上。 realloc(s.buffer, s.size + BUFSIZE) == NULL不是使用realloc的正确方法。从here获取的realloc的返回值为:

  

成功完成后,大小不等于0,realloc()必须   返回指向(可能移动的)分配空间的指针。如果大小是   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用于跟踪缓冲区中剩余的空间。这是读取的量,并在缓冲区大小增加时重置。由于您realloc - 更多空间,您不希望执行之前建议的典型(BUFSIZE-total_read)模式,尽管这是人们看到的典型模式。

如果read始终返回所请求的数据量,您将不会再看到此问题。