(这段代码不是我写的,是我的教授写的。)我在看我的教授写的一些代码,除了一件事之外,这对我来说都是有意义的。(因为我们的时间不多了,他根本不想释放任何内存),然而,他正在用地址杀菌剂进行编译。但是当他运行代码时,没有显示地址消毒程序错误警告?
我们在Ubuntu机器上运行gcc 9.3。当我注释掉add_line函数时,它会抛出泄漏,仅用于crnt。我想lines不会抛出内存泄漏,因为它是在全局空间中声明的?但是,当调用crnt函数时,为什么add_line不抛出内存泄漏呢?
(此外,下面是所使用的编译标志。-g -std=c99 -Wall -Wvla -fsanitize=address,undefined)
以下是代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define DEBUG 1
#define BUFSIZE 8
#define LISTLEN 16
char **lines;
int line_count, line_array_size;
void add_line(char *p)
{
if (DEBUG) printf("Adding |%s|\n", p);
if (line_count == line_array_size) {
line_array_size *= 2;
lines = realloc(lines, line_array_size * sizeof(char *));
// TODO: check whether lines is NULL
}
lines[line_count] = p;
line_count++;
}
int main(int argc, char **argv)
{
int fd, bytes;
char buf[BUFSIZE];
char *crnt;
int len;
int pos, start;
// TODO: move array list management to separate functions
lines = malloc(sizeof(char *) * LISTLEN);
if (!lines) {
printf("malloc failed\n");
return EXIT_FAILURE;
}
line_array_size = LISTLEN;
line_count = 0;
if (argc > 1) {
fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror(argv[1]);
return EXIT_FAILURE;
}
} else {
fd = 0;
}
crnt = NULL;
len = 0;
while ((bytes = read(fd, buf, BUFSIZE)) > 0) {
// read buffer and break file into lines
start = 0;
for (pos = 0; pos < bytes; pos++) {
if (buf[pos] == '\n') {
if (crnt == NULL) {
len = pos - start;
crnt = malloc(len + 1);
memcpy(crnt, &buf[start], len);
} else {
len += pos;
crnt = realloc(crnt, len + 1);
memcpy(&crnt[len - pos], buf, pos);
}
crnt[len] = '\0';
// add_line(crnt); <------------- When I uncomment this line, no address-sanitizer leak is detected. With this line commented, asan does throw a leak only for the crnt variable. Why is that?
crnt = NULL;
start = pos + 1;
}
}
if (start < pos) {
if (crnt == NULL) {
len = pos - start;
crnt = malloc(len + 1);
memcpy(crnt, &buf[start], len);
} else {
int newlen = len + (pos - start);
crnt = realloc(crnt, newlen + 1);
memcpy(&crnt[len], &buf[start], pos - start);
len = newlen;
}
crnt[len] = '\0'; // technically unnecessary
}
}
if (bytes == -1) {
perror("read");
return EXIT_FAILURE;
}
// if we reach here, we have read the entire file
// sort and print the list
return 0;
}发布于 2022-03-10 06:15:09
这里的问题是“内存泄漏”的定义。我本想引用LeakSanitizer文档中的一个章节,其中提供了对这个概念的清晰而精确的定义,这似乎是它的操作的基础,但我找不到,所以您将不得不忍受一些我的预测。
动态分配的内存区域(即与malloc或朋友一起)在没有可能成为freed的情况下泄漏。换句话说,如果程序在分配空闲之前分配内存并丢弃地址,内存就泄漏了。
这与你可能认为的定义有很大的不同。您可能会认为,如果程序终止而不释放所分配的每一个内存块,内存就会泄漏。这当然是一个可能的定义,我不会批评它(很多),但实际上并不是很精确。
什么时候程序结束了?当main()返回时,它并没有真正终止,因为您仍然可以在atexit()中注册清理函数,并且这些函数直到main()返回之后才会执行。(或者当exit()被调用时,这实际上是一回事。)在我看来,准确地使用atexit()函数是非常普遍的(尽管在我看来是毫无意义的),因为free()对象在exit()之前可能还没有被释放。
好的,您不能通过检查当free返回时内存分配是否是main d来检查内存分配是否已被释放。如果你想这样做,你需要把测试推迟到真正可能的最后一刻。但在最后一个可能的时刻,进程即将不复存在,操作系统将回收进程使用的所有内存,包括内存分配库获得的任何资源。所以在最后一个可能的时刻,没有内存泄漏,因为没有内存。
(有些嵌入式系统没有独立进程、内存等概念,所以我在那里写的东西可能并不适用于所有可能的计算系统。但它适用于实现AddressSanitizer的所有内容。)
一个关键问题是,atexit()处理程序需要能够找到它正在清理的对象,而且由于它在main()结束后执行,所以它不能使用任何自动(即堆栈分配)对象。只有具有静态生存期的对象才对其可用。因此,要使它能够执行其任务,在终止时要清理的对象的地址必须存储在全局内存中。如果该区域的内存没有存储在某个持久的位置,则内存已经泄漏(根据我上面的定义),我们实际上不必等待atexit是否能够管理内存。
这让我们回到我所说的内存泄漏的一个可行定义:动态分配的内存,其地址不再存在于可执行文件中。这个内存区域不能再使用了,所以它是垃圾,但它不能是freed,因为程序不知道它的地址是什么。
lines数组是一个全局变量。事实上,你在问题中指出了这一点:
,我想
lines不会抛出内存泄漏,因为它是在全局空间中声明的?
是这样的。lines是一个全局变量,因此即使在main()返回之后,它的内容仍然是可访问的。不仅它的内容是可访问的,它所指向的数组中的某个对象所指向的任何内存也是可访问的。如果愿意,可以free atexit处理程序中保存的行:
void cleanup(void) {
for (int i = 0; i < line_count; ++i) { free(lines[i]); }
free(lines);
}(要使用它,您只需要在初始化atexit(cleanup)和line_count之后调用lines。)
因此,我们需要:
,但是当调用
add_line函数时,为什么crnt不抛出内存泄漏呢?
crnt包含动态分配缓冲区的地址,该缓冲区包含当前行。如果调用add_line(crnt),则该指针存储在lines中。因此,它可以用于清理功能,如上面所示。您可以在方便的情况下将crnt设置为NULL,因为它不再是指向该缓冲区的唯一指针。
但是,如果不调用add_line,那么crnt是指向该缓冲区的唯一指针,当您将crnt设置为NULL时,就不再有指向缓冲区的指针了。缓冲区泄漏了,AddressSanitizer在那里告诉您。(即使您没有将AddressSanitizer设置为NULL,crnt也会遇到这个问题,因为当main()返回或调用exit()时,crnt就不再存在了,此时地址已经丢失。或者如果您用不同的分配地址覆盖crnt。)
对于一个简单得多的例子,尝试这两个非常相似的程序:
内存泄漏
#include <stdlib.h>
int main(void) {
void* megabyte = malloc(1<<20);
(void)megabyte; /* Suppress unused variable warning */
}无内存泄漏
#include <stdlib.h>
void* megabyte;
int main(void) {
megabyte = malloc(1<<20);
}请注意,Valgrind工具可以报告内存,就像第二个示例中的megabyte一样,它永远不是freed,尽管它仍然可以在free认为的执行结束时到达。但在默认情况下,它不会这么做。如果您在第二个程序上运行--show-leak-kinds=all --leak-check=full标志,它将报告一个兆字节的内存“仍然可以访问”。(我认为,要想尝试val差制,就必须在没有AddressSanitizer的情况下编译程序。这两个工具并不完全兼容。)
https://stackoverflow.com/questions/71418808
复制相似问题