read() 失败,地址错误,valgrind 显示系统调用参数 read(buf) 指向无法寻址的字节
read() fails with Bad address, valgrind shows Syscall param read(buf) points to unaddressable byte(s)
我有一个函数可以使用 read()
系统调用和 return 从文件中读取数据的 char
指针来读取文件。如有必要,函数会重新分配 space。在特定点之后,读取失败并出现错误 "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()
成功了怎么办?
根据 man page、
The realloc()
function returns a pointer to the newly allocated memory, which is suitably aligned for any kind of variable and may be different from ptr
, or NULL if the request fails. If size
was equal to 0
, either NULL
or a pointer suitable to be passed to free()
is returned. If realloc()
fails the original block is left untouched; it is not freed or moved.
这意味着,您将丢失新分配的内存,然后使用 free()
d 内存。
我想,您可能期望 realloc()
会调整 s.buffer
本身的大小,但是,恐怕这里不是这种情况。
解决方法:
您应该将 realloc()
的 return 值收集到一个临时变量中,对照 NULL
检查,如果不是 NULL
,则将其赋值回指针s.buffer
.
FWIW,不要使用原始指针本身来收集realloc()
的return值,以防失败,你会也失去了实际记忆。
问题可能与您使用 realloc
的方式有关。 realloc(s.buffer, s.size + BUFSIZE) == NULL
不是 realloc
的正确使用方法。从 here 中获取的 realloc
的 return 值是:
Upon successful completion with a size not equal to 0, realloc() shall
return a pointer to the (possibly moved) allocated space. If size is
0, either a null pointer or a unique pointer that can be successfully
passed to free() shall be returned. If there is not enough available
memory, realloc() shall return a null pointer and
set errno to ENOMEM.
关键部分是 realloc
可以移动分配的 space 及其数据。所以你不能只检查 return 值是否为 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
的 return 值更新指针。如果数据没有被移动,realloc
returns 内存地址传递给它;如果它被移动,realloc
将 return 一个新地址。
更新:
您可能没有遇到的另一个问题是如何处理 read
的 return 值。如果 read
returned 比要求的少,你不会 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
用于跟踪缓冲区中还剩多少space。这是读取的数量,并在缓冲区大小增加时重置。由于您 realloc
-ing 更多 space 您不想执行之前建议的典型 (BUFSIZE-total_read)
模式,尽管这是人们看到的典型模式。
同样,如果 read
总是 return 请求的数据量,您将不会看到此问题。
我有一个函数可以使用 read()
系统调用和 return 从文件中读取数据的 char
指针来读取文件。如有必要,函数会重新分配 space。在特定点之后,读取失败并出现错误 "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()
成功了怎么办?
根据 man page、
The
realloc()
function returns a pointer to the newly allocated memory, which is suitably aligned for any kind of variable and may be different fromptr
, or NULL if the request fails. Ifsize
was equal to0
, eitherNULL
or a pointer suitable to be passed tofree()
is returned. Ifrealloc()
fails the original block is left untouched; it is not freed or moved.
这意味着,您将丢失新分配的内存,然后使用 free()
d 内存。
我想,您可能期望 realloc()
会调整 s.buffer
本身的大小,但是,恐怕这里不是这种情况。
解决方法:
您应该将 realloc()
的 return 值收集到一个临时变量中,对照 NULL
检查,如果不是 NULL
,则将其赋值回指针s.buffer
.
FWIW,不要使用原始指针本身来收集realloc()
的return值,以防失败,你会也失去了实际记忆。
问题可能与您使用 realloc
的方式有关。 realloc(s.buffer, s.size + BUFSIZE) == NULL
不是 realloc
的正确使用方法。从 here 中获取的 realloc
的 return 值是:
Upon successful completion with a size not equal to 0, realloc() shall return a pointer to the (possibly moved) allocated space. If size is 0, either a null pointer or a unique pointer that can be successfully passed to free() shall be returned. If there is not enough available memory, realloc() shall return a null pointer and set errno to ENOMEM.
关键部分是 realloc
可以移动分配的 space 及其数据。所以你不能只检查 return 值是否为 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
的 return 值更新指针。如果数据没有被移动,realloc
returns 内存地址传递给它;如果它被移动,realloc
将 return 一个新地址。
更新:
您可能没有遇到的另一个问题是如何处理 read
的 return 值。如果 read
returned 比要求的少,你不会 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
用于跟踪缓冲区中还剩多少space。这是读取的数量,并在缓冲区大小增加时重置。由于您 realloc
-ing 更多 space 您不想执行之前建议的典型 (BUFSIZE-total_read)
模式,尽管这是人们看到的典型模式。
同样,如果 read
总是 return 请求的数据量,您将不会看到此问题。