【问题标题】:How to detect if a block of memory already freed如何检测一块内存是否已经释放
【发布时间】:2022-08-16 08:27:30
【问题描述】:

我已经知道有no way 知道指针目标是否仍然是它的有效分配已经被释放,所以我试图使用指向指针的指针来解决这个问题,但它没有工作。

我的目标只是让print_block() 检测block 指针是否为无效的或不。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void free_block(u_int8_t **_block) {

    // Dereference
    u_int8_t *block = *_block;

    // Info
    printf(\"free_block()\\t-> %p Point To %p \\n\", &block, block);

    // Free Block
    free(block);
    block = NULL;
}

void print_block(u_int8_t **_block) {

    // Dereference
    u_int8_t *block = *_block;

    // Detectc if this block is freed
    // This is the objective of this code
    if(block == NULL) {

        printf(\"print_block()\\t-> %p Is Null.\\n\", block);
        return;
    }

    // Info
    printf(\"print_block()\\t-> %p Point To %p -> \", &block, block);

    // Print byte by byte
    u_int8_t *p = block;
    for(int i = 0; i < 3; i++) {

        printf(\"0x%02X \", *(u_int8_t *)p);
        p++;
    }
    printf(\"\\n\");
}

int main(void) {

    // Allocat a block in the memory
    u_int8_t *block = malloc(3 * sizeof(u_int8_t));

    // Set all to zeros
    memset(block, 0x00, 3);

    // Info
    printf(\"Main()\\t\\t\\t-> %p Point To %p \\n\", &block, block);

    // Print the block content
    print_block(&block);

    // Free the block
    free_block(&block);

    // Print the block content gain
    // This shold print Null because
    // we freed the block.
    print_block(&block);

    return 0;
}

结果

Main()          -> 0x7fffd549cc58 Point To 0xfa42a0 
print_block()   -> 0x7fffd549cc28 Point To 0xfa42a0 -> 0x00 0x00 0x00 
free_block()    -> 0x7fffd549cc60 Point To 0xfa42a0 
print_block()   -> 0x7fffd549cc28 Point To 0xfa42a0 -> 0xA4 0x0F 0x00 
  • block = NULL; 应该是 *_block = NULL;block = NULL; 什么都不做,因为 block 即将超出范围。
  • 此外,printf(\"free_block()\\t-&gt; %p Point To %p \\n\", &amp;block, block); 对于第一个参数&amp;block 实际上毫无价值。谁在乎局部变量的地址?这两个参数应该是_block*_block。老实说,block 在该功能中的用处根本值得商榷。
  • 请注意,通常不应创建以下划线开头的函数、变量、标记或宏名称。 C11 §7.1.3 Reserved identifiers 的一部分说:-以下划线和大写字母或另一个下划线开头的所有标识符始终保留用于任何用途。所有以下划线开头的标识符始终保留用作普通和标记名称空间中具有文件范围的标识符。另见What does double underscore (__const) mean in C?
  • 你可以看看 Steve Maguire Writing Solid Code: 20th Anniversary 2nd Edn 2013。有些人非常不喜欢这本书;我认为它非常有用。它包括包装内存管理函数的代码,以便您可以跟踪给定指针是否仍然有效,并讨论了使用它的一些陷阱。值得注意的是,您必须安排包装任何分配内存的函数——例如strdup()——并确保在代码中使用包装器。
  • 你想解决什么问题,内存调试器还没有解决?

标签: c pointers memory


【解决方案1】:

首先,您需要知道函数free(p) 只会标记一块内存以供任何新分配使用,仅此而已,仅此而已;虽然你的指针p 仍然有效的, 你可以即使块已经被释放,也要使用它。

关于你的问题“如何检测一块内存是否已被释放?" C 中的简短回答是没有标准方法。但是您可以编写自己的指针跟踪器来检测内存块是否已被释放,这并不难做到,这是一个示例:

void *ptr_list[64];
int ptr_position = 0;

bool ptr_exist(void *p) {
    if(p == NULL)
        return false;
    for(int i = 0; i < ptr_position; i++) {
        if(ptr_list[i] == p)
            return true;
    }
    return false;
}

void ptr_add(void *p) {
    if(p == NULL)
        return;
    if(!ptr_exist(p)) {
        for(int i = 0; i < ptr_position; i++) {
            if(ptr_list[i] == NULL) {
                ptr_list[i] = p;
                return;
            }
        }
        ptr_list[ptr_position] = p;
        ptr_position++;
    }
}

void ptr_free(void **p) {
    if(*p == NULL)
        return;
    for(int i = 0; i < ptr_position; i++) {
        if(ptr_list[i] == *p) {
            ptr_list[i] = NULL;
        }
    }
    for(int i = ptr_position; i >= 0; i--) {
        if(ptr_list[i] == NULL) {
            ptr_position = i;
            break;
        }
    }
    free(*p);
    *p = NULL;
}

要使用它,只需在分配一块内存后添加您的指针使用ptr_add() 到跟踪器,当你想释放它时,使用ptr_free()。最后,您可以随时从任何线程检查此内存块是否仍然有效或不使用ptr_exist()

完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// -- Pointers Tracker --
void *ptr_list[64];
int ptr_position = 0;

bool ptr_exist(void *p) {
    if(p == NULL)
        return false;
    for(int i = 0; i < ptr_position; i++) {
        if(ptr_list[i] == p)
            return true;
    }
    return false;
}

void ptr_add(void *p) {
    if(p == NULL)
        return;
    if(!ptr_exist(p)) {
        for(int i = 0; i < ptr_position; i++) {
            if(ptr_list[i] == NULL) {
                ptr_list[i] = p;
                return;
            }
        }
        ptr_list[ptr_position] = p;
        ptr_position++;
    }
}

void ptr_free(void **p) {
    if(*p == NULL)
        return;
    for(int i = 0; i < ptr_position; i++) {
        if(ptr_list[i] == *p) {
            ptr_list[i] = NULL;
        }
    }
    for(int i = ptr_position; i >= 0; i--) {
        if(ptr_list[i] == NULL) {
            ptr_position = i;
            break;
        }
    }
    free(*p);
    *p = NULL;
}
// ----------------------

void free_block(u_int8_t *block) {

    // Info
    printf("free_block()\t-> %p Point To %p \n", &block, block);

    // Free Block
    // free(block);
    // block = NULL;
    ptr_free((void *)&block);
}

void print_block(u_int8_t *block) {

    // Detectc if this block is freed
    // This is the objective of this code
    if(!ptr_exist(block)) {

        printf("print_block()\t-> %p Is Null.\n", block);
        return;
    }

    // Info
    printf("print_block()\t-> %p Point To %p -> ", &block, block);

    // Print byte by byte
    u_int8_t *p = block;
    for(int i = 0; i < 3; i++) {

        printf("0x%02X ", *(u_int8_t *)p);
        p++;
    }
    printf("\n");
}

int main(void) {

    // Allocat a block in the memory
    u_int8_t *block = malloc(3 * sizeof(u_int8_t));

    // Add it to the tracker
    ptr_add((void *)block);

    // Set all to zeros
    memset(block, 0x00, 3);

    // Info
    printf("Main()\t\t\t-> %p Point To %p \n", &block, block);

    // Print the block content
    print_block(block);

    // Free the block
    free_block(block);

    // Print the block content gain
    // This shold print Null because
    // we freed the block.
    print_block(block);

    return 0;
}

【讨论】:

  • 感谢您对 free() 的解释,它回答了我的问题。你的代码解决了我的问题。再次感谢你。
【解决方案2】:

您可以使用Block struct 和一点纪律。

举个例子

typedef struct
{
    size_t   size;
    uint8_t  fill;
    uint8_t* data;

} Block;

Block* free_block(Block*);
Block* get_block(size_t size, uint8_t fill_value);
void   print_block(Block* block, const char* title);

  • 使函数对指向Block的指针进行操作,封装数据
  • make free_block() return NULL 以简化使块指针无效的任务
  • 测试print_block()里面的块地址
  • print_block() 中使用可选标题

以主要为例

int main(void)
{
    const int test_size = 32;
    Block* block = get_block(test_size, 0);  // zero-filled
    printf(
        "Main()\t\t\t-> Pointer at %p points to data at "
        "%p\n",
        &block, block->data);
    print_block(block, "\nBlock contents:");
    block = free_block(block);
    print_block(block, "\nafter free()");
    return 0;
}

也许您会发现这种方式更易于阅读。

main 的输出如上

Main()                  -> Pointer at 00CFF754 points to data at 00E96C70

Block contents
print_block()-> valid block data of size 32 at 00E96C70
                fill char is 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

after free()
print_block()   -> block is null.

完整代码

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
{
    size_t   size;
    uint8_t  fill;
    uint8_t* data;

} Block;

Block* free_block(Block*);
Block* get_block(size_t size, uint8_t fill_value);
void   print_block(Block* block, const char* title);

int main(void)
{
    const int test_size = 32;
    Block* block = get_block(test_size, 0);  // 3 bytes, zero filled
    printf(
        "Main()\t\t\t-> Pointer at %p points to data at "
        "%p\n",
        &block, block->data);
    print_block(block, "\nBlock contents:");
    block = free_block(block);
    print_block(block, "\nafter free()");
    return 0;
}

Block* free_block(Block* block)
{
    if (block == NULL) return NULL;
    if (block->data == NULL) return NULL;
    free(block->data);  // data is free
    free(block);
    return NULL;
}

Block* get_block(size_t size, uint8_t fill)
{
    Block* block = malloc(sizeof(Block));
    if (block == NULL) return NULL;
    block->data = malloc(size * sizeof(uint8_t));
    if (block->data == NULL) return NULL;
    memset(block->data, fill, size);
    block->fill = fill;
    block->size = size;
    return block;
}

void print_block(Block* block, char* title)
{
    if (title != NULL) printf("%s\n", title);
    if (block == NULL)
    {
        printf("print_block()\t-> block is null.\n");
        return;
    }
    printf(
        "print_block()->\tvalid block data of size %d at "
        "%p\n\t\tfill char is %02X\n\n",
        block->size, block->data, block->fill);
    // Print byte by byte
    const int nc = 16; // 16 bytes per line
    size_t    j  = 1;
    for (size_t i = 0; i < block->size; i += 1)
    {
        printf("%02X ", block->data[i]);
        if (j >= nc)
            printf("\n"), j = 1;
        else
            j += 1;
    }
    if ( j != 1 ) printf("\n");
    return;
}

【讨论】:

  • 您的代码解决了捕获block is null 的问题,不需要指针,我可以在多线程环境中使用它,谢谢 :)
  • 我刚刚发现在多线程应用程序中执行此操作是另一种动物.. 我试过这个replit.com/@ShellX3/PointerMultiThreading#main.c
  • 您将需要某种锁来保护指针以使其在多线程应用程序中工作
  • 我做了类似@chqrlie track_add_pointer() 示例的操作,现在运行良好,感谢您的所有解释。
【解决方案3】:

没有标准的方法来判断一个指针是否有效,它是否指向一个分配的对象,也没有它是否已经被释放。

然而,可以在分配函数之上设计一个框架来跟踪所有分配和释放,并维护一个有效指针列表。在此列表中查找指针值将告诉您指针是否指向有效分配的块以及可能从堆中请求的大小。

这是一个简单的实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void track_add_pointer(void *p, size_t size);
int track_find_pointer(void *p, size_t *sizep);
void track_remove_pointer(void *p);

void *tmalloc(size_t size) {
    void *p = (malloc)(size);
    if (p) {
        track_add_pointer(p, size);
    }
    return p;
}

void *tcalloc(size_t size, size_t nmemb) {
    void *p = (calloc)(size, nmemb);
    if (p) {
        track_add_pointer(p, size * nmemb);
    }
    return p;
}

void *trealloc(void *p, size_t size) {
    if (p) {
        void *newp = NULL;
        if (!track_find_pointer(p, NULL)) {
            fprintf(stderr, "trealloc: invalid pointer %p\n", p);
        } else {
            if (size == 0) {
                (free)(p);
            } else {
                newp = (realloc)(p, size);
            }
            if (newp != p) {
                if (p) track_remove_pointer(p);
                if (newp) track_add_pointer(newp);
            }
        }
        return newp;
    } else {
        return tmalloc(size);
    }
}

void tfree(void *p) {
    if (p) {
        if (!track_find_pointer(p, NULL)) {
            fprintf(stderr, "tfree: invalid pointer %p\n", p);
        } else {
            (free)(p);
            track_remove_pointer(p);
        }
    }
}

char *tstrdup(const char *s) {
    char *p = NULL;
    if (s) {
        size_t len = strlen(s);
        p = tmalloc(len + 1);
        if (p) {
            memcpy(p, s, len + 1);
        }
    } else {
        fprintf(stderr, "tstrdup: null pointer\n");
    }
    return p;
}

char *tstrndup(const char *s, size_t n) {
    char *p = NULL;
    if (s) {
        size_t len = strnlen(s, n);
        p = tmalloc(len + 1);
        if (p) {
            memcpy(p, s, len);
            p[len] = '\0';
        }
    } else {
        fprintf(stderr, "tstrndup: null pointer\n");
    }
    return p;
}

【讨论】:

  • 谢谢你的解释,但我想if(p) 这不是一个好习惯,这是一个证明代码godbolt.org/z/dMb4ePrhn。如果我错了,请纠正我。
  • 应该是if(p != NULL)
  • 没关系,我用你的代码玩得更多,看起来if(p)成功也像if(p != NULL),我仍然不知道什么是最佳实践:)
  • @ShellX3:if (p)if (p != NULL) 完全等价,并且在 C 中都是惯用的。没有最佳实践,但是对于给定的项目或公司内部可能存在推荐其中一种的当地惯例,但这实际上是风格和个人选择的问题。我个人更喜欢if (p)的简洁。保持一致也很重要:避免在同一个项目中混合样式。
  • 我明白了,这是真的,即使是 ASM 生成的代码看起来也几乎一样。
猜你喜欢
  • 2010-12-12
  • 1970-01-01
  • 1970-01-01
  • 2010-10-09
  • 2021-10-03
  • 1970-01-01
  • 2017-02-16
  • 2012-09-24
  • 2011-11-03
相关资源
最近更新 更多