【问题标题】:read() fails with Bad address, valgrind shows Syscall param read(buf) points to unaddressable byte(s)read() 因地址错误而失败,valgrind 显示 Syscall 参数 read(buf) 指向不可寻址的字节
【发布时间】:2015-09-21 12:46:30
【问题描述】:

我有一个函数可以使用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;
}

运行它会得到以下结果:

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 个字节。

【问题讨论】:

  • 谢谢。这个问题可以作为“如何写一个好问题”的例子。
  • 是否有充分的理由不使用fstat() 来确定文件大小、分配空间,然后一次全部读取?
  • 我正在编写一个爬虫作为一个辅助项目,想看看在不知道响应大小的情况下从套接字读取是如何工作的,所以我想我会先尝试从文件中读取。
  • 一些注意事项:1) 表达式'sizeof(char)' 总是返回1,并且对malloc() 的调用没有影响。它只是使代码混乱。建议从对 malloc() 的任何调用中删除该表达式 2) 对 read() 的调用始终要求 BUF_SIZE 字节,但在读取任何字节后,输入缓冲区中没有可用的 BUF_SIZE 字节。建议使用:'while ((nread = read(fd, s.buffer + total_read, BUFSIZE-total_read)) != 0) {'
  • 一些注意事项,继续 3) 传递一个完整的结构不是一个好主意,原因有几个。建议传递/返回指向结构的指针。 4) 在成功调用 malloc() 之后,如果出现错误,则需要将指向已分配内存的指针传递给 free()。 5) 对 realloc() 的调用没有保存从 realloc() 返回的指针。结果是 s.buffer 仍然指向旧分配的内存,在分配的内存复制到新分配的内存后,它被释放了。

标签: c linux realloc


【解决方案1】:

问题可能与您使用realloc 的方式有关。 realloc(s.buffer, s.size + BUFSIZE) == NULL 不是使用realloc 的正确方法。 realloc取自here的返回值为:

在大小不等于 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-ing 更多空间,您不想执行之前建议的典型 (BUFSIZE-total_read) 模式,尽管这是人们看到的典型模式。

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

【讨论】:

  • 未能解决 read() 能够输入超出分配的内存输入缓冲区末尾的字符的问题。
  • @user3629249 作者总是重新分配以添加额外的空间,所以不需要这样做(BUFSIZE-total_read),我第一次阅读时认为这是一个错误写有这是典型的模式你看,但在这种情况下没有必要。
  • @user3629249 实际上在查看了问题代码之后,我们都错了,因为 realloc 并不总是在每次读取后发生。正在更新答案。
【解决方案2】:

乍一看,您对realloc() 的使用看起来不对。

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

此语句检查realloc() 是否成功。如果失败,这将处理这种情况。很好。

如果realloc()成功了呢?

根据man page

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

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

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

解决方案

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

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

【讨论】:

  • 未能解决 read() 能够输入超过分配的内存输入缓冲区末尾的字符的问题。
猜你喜欢
  • 1970-01-01
  • 2023-03-19
  • 2017-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-04
  • 2011-02-13
相关资源
最近更新 更多