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

read()因地址错误而失败,valgrind显示Syscall param read(buf)指向不可寻址字节,c,linux,realloc,C,Linux,Realloc,我有一个函数,可以使用read()系统调用读取一个文件,并返回一个char指针和从文件读取的数据。如果需要,该函数将重新分配空间。在特定点之后,读取失败,错误为“坏地址”。出现故障的最低代码如下所示: #include <errno.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> const unsigned BUFSIZ

我有一个函数,可以使用
read()
系统调用读取一个文件,并返回一个
char
指针和从文件读取的数据。如果需要,该函数将重新分配空间。在特定点之后,读取失败,错误为“坏地址”。出现故障的最低代码如下所示:

#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;
}
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,但我不明白是什么导致了地址错误。上次执行
realloc()
后,
read()
写入的缓冲区是否已损坏


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()
的返回值,如果失败,您也将丢失实际内存。

问题可能在于如何使用
realloc
realloc(s.buffer,s.size+BUFSIZE)=NULL
不是使用
realloc
的正确方法。取自的
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
更多内存,进一步读取可能会读取超过缓冲区的内存。您现在可能没有遇到这种情况,因为只有当
读取
没有失败并且读取的数据量不小于请求的数据量时,才会显示这种情况。包括
realloc
fix的解决方案如下:

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\u remaining
用于跟踪缓冲区中剩余的空间。这是读取的量,当缓冲区大小增加时会重置。由于您正在
realloc
-占用更多空间,因此您不希望执行先前建议的典型
(BUFSIZE-total_read)
模式,尽管这是您看到的典型模式


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

谢谢。这个问题可以作为“如何写一个好问题”的例子。是否有充分的理由不使用它来确定文件大小、分配空间、,然后一次全部读取?我正在编写一个爬虫程序作为一个辅助项目,想看看在不知道响应大小的情况下从套接字读取是如何工作的,所以我想先尝试从文件读取。注意:1)表达式“sizeof(char)”总是返回1,并且对malloc()的调用没有影响。它只是把代码弄乱了。建议从对malloc()的任何调用中删除该表达式2)对read()的调用总是请求BUF_大小的字节,但在读取任何字节后,输入缓冲区中没有可用的BUF_大小的字节。建议使用:'while((nread=read(fd,s.buffer+total_read,BUFSIZE-total_read))!=0){一些注释,继续3)传递完整的结构不是一个好主意,原因有几个。建议在成功调用malloc()后传递/返回指向结构的指针。4)然后在出现错误时,需要将指向已分配内存的指针传递给free()。5)对realloc()的调用没有保存从realloc()返回的指针。结果是s.buffer仍指向旧的已分配内存,该内存在已分配内存复制到新的已分配内存后被释放。无法解决read()的问题,因为它能够输入超出已分配内存输入缓冲区末尾的字符。无法解决read()的问题能够在分配的内存输入缓冲区结束后输入字符。@user3629249作者总是重新分配以添加额外的空间,所以不需要这样做(BUFSIZE-total_read),我认为
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;
            }
        }
    }