繁体   English   中英

检查指针是否已分配内存

[英]Checking if a pointer is allocated memory or not

我们可以检查传递给函数的指针是否在C中分配了内存?

我在C中使用了自己的函数,它接受了一个字符指针 - buf [指向缓冲区的指针]和大小 - buf_siz [缓冲区大小]。 实际上在调用此函数之前,用户必须创建一个缓冲区并为其分配buf_siz的内存。

由于用户可能忘记进行内存分配并只是将指针传递给我的函数,我想检查一下。 那么有什么方法可以检查我的函数,看看传递的指针是否真的分配了buf_siz的内存量。??

编辑1: 似乎没有标准库来检查它..但是有任何脏的黑客来检查它.. ??

编辑2: 我知道我的功能将由一个优秀的C程序员使用...但我想知道我们是否可以检查..如果我们可以,我想听听它...

结论:因此无法检查特定指针是否在函数内分配了内存

您无法检查,除了一些特定于实现的黑客攻击。

指针除了指向的地方之外没有任何信息。 您可以做的最好的事情是“我知道这个特定的编译器版本如何分配内存,所以我将取消引用内存,将指针移回4个字节,检查大小,确保它匹配......”等等。 您无法以标准方式执行此操作,因为内存分配是实现定义的。 更不用说他们可能根本没有动态分配它。

你只需假设你的客户知道如何在C中编程。我能想到的唯一解决方案是自己分配内存并返回它,但这几乎不是一个小小的改变。 (这是一个更大的设计变化。)

对于特定于平台的解决方案,您可能对Win32函数IsBadReadPtr (以及其他类似函数)感兴趣。 此函数将能够(几乎)预测从特定内存块读取时是否会出现分段错误。

但是,这在一般情况下不能保护您,因为操作系统对C运行时堆管理器一无所知,并且如果调用者传入的缓冲区不是您期望的那么大,那么堆块的其余部分从操作系统的角度来看,它将继续可读。

下面的代码是我用过一次检查某些指针是否试图访问非法内存的代码。 该机制是诱导SIGSEGV。 SEGV信号更早地被重定向到私有函数,它使用longjmp返回程序。 它有点像黑客但它有效。

代码可以改进(使用'sigaction'代替'signal'等),但它只是给出一个想法。 它也可以移植到其他Unix版本,对于Windows我不确定。 请注意,不应在程序中的其他位置使用SIGSEGV信号。

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <signal.h>

jmp_buf jump;

void segv (int sig)
{
  longjmp (jump, 1); 
}

int memcheck (void *x) 
{
  volatile char c;
  int illegal = 0;

  signal (SIGSEGV, segv);

  if (!setjmp (jump))
    c = *(char *) (x);
  else
    illegal = 1;

  signal (SIGSEGV, SIG_DFL);

  return (illegal);
}

int main (int argc, char *argv[])
{
  int *i, *j; 

  i = malloc (1);

  if (memcheck (i))
    printf ("i points to illegal memory\n");
  if (memcheck (j))
    printf ("j points to illegal memory\n");

  free (i);

  return (0);
}

我曾经在64位Solaris上使用过脏黑客。 在64位模式下,堆从0x1 0000 0000开始。通过比较指针,我可以确定它是否是数据或代码段中的指针p < (void*)0x100000000 ,堆中的指针p > (void*)0x100000000或内存映射区域中的指针(intptr_t)p < 0 (mmap返回可寻址区域顶部的地址)。 这允许我的程序在同一个映射中保存已分配和内存映射的指针,并让我的map模块释放正确的指针。

但是这种技巧非常难以移植,如果你的代码依赖于类似的东西,那么现在是时候重新思考代码的架构了。 你可能做错了什么。

我总是初始化指向null值的指针。 因此,当我分配内存时,它会改变。 当我检查是否已经分配了内存时,我做pointer != NULL 当我释放内存时,我也将指针设置为null。 我想不出有什么方法可以判断是否分配了足够的内存。

这并不能解决您的问题,但您必须相信,如果有人编写C程序,那么他就足够熟练地做到了。

我知道这是一个老问题,但在C中几乎任何事情都是可能的。这里有一些hackish解决方案,但确定内存是否已正确分配的有效方法是使用oracle代替malloccallocreallocfree 这与测试框架(例如cmocka)可以检测内存问题(seg错误,不释放内存等)的方式相同。 您可以维护分配时分配的内存地址列表,并在用户想要使用您的功能时查看此列表。 我为自己的测试框架实现了非常类似的东西。 一些示例代码:

typedef struct memory_ref {
    void       *ptr;
    int        bytes;
    memory_ref *next;
}

memory_ref *HEAD = NULL;

void *__wrap_malloc(size_t bytes) {
    if(HEAD == NULL) {
        HEAD = __real_malloc(sizeof(memory_ref));
    }

    void *tmpPtr = __real_malloc(bytes);

    memory_ref *previousRef = HEAD;
    memory_ref *currentRef = HEAD->next;
    while(current != NULL) {
        previousRef = currentRef;
        currentRef = currentRef->next;
    }

    memory_ref *newRef = (memory_ref *)__real_malloc(sizeof(memory_ref));
    *newRef = (memory_ref){
        .ptr   = tmpPtr,
        .bytes = bytes,
        .next  = NULL
    };

    previousRef->next = newRef;

    return tmpPtr;
}

你会有类似的callocreallocfree函数,每个包装器前缀为__wrap_ 真正的malloc可以通过使用__real_malloc (类似于你正在包装的其他函数)。 无论何时想要检查内存是否实际分配,只需遍历链接的memory_ref列表并查找内存地址。 如果你找到它并且它足够大,你肯定知道内存地址不会崩溃你的程序; 否则,返回错误。 在程序使用的头文件中,您将添加以下行:

extern void *__real_malloc  (size_t);
extern void *__wrap_malloc  (size_t);
extern void *__real_realloc (size_t);
extern void *__wrap_realloc (size_t);
// Declare all the other functions that will be wrapped...

我的需求是相当简单,所以我实现了一个非常基本的实现,但你可以想像这可以扩展到有更好的跟踪系统(比如创建一个struct ,用于跟踪的存储单元中除了大小)。 然后你只需编译代码

gcc src_files -o dest_file -Wl,-wrap,malloc -Wl,-wrap,calloc -Wl,-wrap,realloc -Wl,-wrap,free

缺点是用户必须使用上述指令编译其源代码; 然而,这远远不是我所看到的更糟糕的。 分配和释放内存有一些开销,但在添加安全性时总会有一些开销。

不,一般来说没有办法做到这一点。

此外,如果您的接口只是“将指针传递给我将放置东西的缓冲区”,则调用者可以选择分配内存,而是使用静态分配的固定大小缓冲区或自动变量等。 或者它可能是指向堆上较大对象的一部分的指针。

如果你的接口专门说“传递指向已分配内存的指针(因为我要解除分配它)”,那么你应该期望调用者会这样做。 如果不这样做,你就无法可靠地发现。

您可以尝试的一个方法是检查指针是否指向堆栈分配的内存。 这通常对您没有帮助,因为分配的缓冲区可能很小或指针指向某个全局内存部分(.bss,.const,...)。

要执行此hack,首先将第一个变量的地址存储在main()中。 稍后,您可以将此地址与特定例程中的本地变量的地址进行比较。 两个地址之间的所有地址都位于堆栈中。

您无法检查标准C中的任何可用内容。即使您的特定编译器提供了这样做的功能,它仍然是一个坏主意。 这是一个原因的例子:

int YourFunc(char * buf, int buf_size);

char str[COUNT];
result = YourFunc(str, COUNT);

正如其他人所说,没有一种标准的方法可以做到这一点。

到目前为止,没有人提到史蒂夫马奎尔的“ 编写固体代码 ”。 虽然在某些方面受到严厉批评,但本书上有关于内存管理主题的章节,并讨论了如何谨慎和完全控制程序中的所有内存分配,你可以在询问并确定给出的指针是否为动态分配内存的有效指针。 但是,如果您计划使用第三方库,您会发现其中很少一些允许您将内存分配例程更改为您自己的,这使这种分析变得非常复杂。

我不知道从库调用中执行此操作的方法,但在Linux上,您可以查看/proc/<pid>/numa_maps 它将显示内存的所有部分,第三列将显示“堆”或“堆栈”。 您可以查看原始指针值以查看它的排列方式。

例:

00400000 prefer:0 file=/usr/bin/bash mapped=163 mapmax=9 N0=3 N1=160
006dc000 prefer:0 file=/usr/bin/bash anon=1 dirty=1 N0=1
006dd000 prefer:0 file=/usr/bin/bash anon=9 dirty=9 N0=3 N1=6
006e6000 prefer:0 anon=6 dirty=6 N0=2 N1=4
01167000 prefer:0 heap anon=122 dirty=122 N0=25 N1=97
7f39904d2000 prefer:0 anon=1 dirty=1 N0=1
7f39904d3000 prefer:0 file=/usr/lib64/ld-2.17.so anon=1 dirty=1 N0=1
7f39904d4000 prefer:0 file=/usr/lib64/ld-2.17.so anon=1 dirty=1 N1=1
7f39904d5000 prefer:0 anon=1 dirty=1 N0=1
7fffc2d6a000 prefer:0 stack anon=6 dirty=6 N0=3 N1=3
7fffc2dfe000 prefer:0

所以高于0x01167000但低于0x7f39904d2000的指针位于堆中。

有一种简单的方法可以做到这一点。 每当你创建一个指针时,在它周围写一个包装器。 例如,如果您的程序员使用您的库来创建结构。

struct struct_type struct_var;

确保他使用你的功能分配内存,如

struct struct_type struct_var = init_struct_type()

如果此struct_var包含动态分配的内存,例如,

如果struct_type的定义是

typedef struct struct_type {
 char *string;
}struct_type;

然后在你的init_struct_type()函数中,执行此操作,

init_struct_type()
{ 
 struct struct_type *temp = (struct struct_type*)malloc(sizeof(struct_type));
 temp->string = NULL;
 return temp;
}

这样,除非他将temp-> string分配给一个值,否则它将保持为NULL。 如果字符串为NULL,则可以签入使用此结构的函数。

还有一件事,如果程序员非常糟糕,他没有使用你的函数,而是直接访问未分配的内存,他不配使用你的库。 只需确保您的文档指定了所有内容。

不,你不能。 您会注意到标准库或其他任何地方都没有这样的功能。 那是因为没有标准的说法。 调用代码只需承担正确管理内存的责任。

一般来说,lib用户负责输入检查和验证。 您可能会在lib代码中看到ASSERT或其他内容,它们仅用于调试perpose。 这是编写C / C ++时的标准方法。 虽然很多程序员都非常谨慎地对他们的lib代码进行检查和验证。 真的“不好”的习惯。 正如IOP / IOD中所述,lib接口应该是契约,并明确lib将做什么和不做什么,以及lib用户应该做什么以及什么不应该做。

一个未初始化的指针就是 - 未初始化的。 它可能指向任何地方或仅仅是无效地址(即未映射到物理或虚拟内存的地址)。

一个实际的解决方案是在指向的对象中有一个有效性签名。 创建一个malloc()包装器,它分配请求的块大小加上签名结构的大小,在块的开头创建一个签名结构,但返回签名后指向该位置的指针。 然后,您可以创建一个获取指针的验证函数,使用负偏移量来获取有效性结构并进行检查。 您当然需要一个相应的free()包装器来通过覆盖有效性签名使块无效,并从分配块的真正开始执行。

作为有效性结构,您可以使用块的大小及其一个补码。 这样,您不仅可以验证块(XOR这两个值并与零比较),但您还可以获得有关块大小的信息。

您可以通过在malloc/malloc.h调用malloc_size(my_ptr)来返回malloc为您的指针分配的大小,如果未分配指针,则返回0。 请记住,malloc会调整已分配块的大小,以确保可以从该指针取消引用最具限制性的类型变量并对齐内存。 因此,如果调用malloc(1)(以及malloc(0)),malloc实际上返回16个字节(在大多数机器上),因为最严格的类型具有16个字节的大小

指针跟踪器,跟踪并检查指针的有效性

用法:

create memory int * ptr = malloc(sizeof(int)* 10);

将指针地址添加到跟踪器Ptr(&ptr);

检查指针失败PtrCheck();

并在代码末尾释放所有跟踪器

PtrFree();

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

struct my_ptr_t { void ** ptr; size_t mem; struct my_ptr_t *next, *previous; };

static struct my_ptr_t * ptr = NULL;

void Ptr(void * p){ 
                struct my_ptr_t * tmp = (struct my_ptr_t*) malloc(sizeof(struct my_ptr_t));
                printf("\t\tcreating Ptr tracker:");    
                if(ptr){ ptr->next = tmp; }
                tmp->previous = ptr;
                ptr = tmp;
                ptr->ptr = p;
                ptr->mem = **(size_t**) ptr->ptr;
                ptr->next = NULL;
                printf("%I64x\n", ptr); 
};
void PtrFree(void){
                    if(!ptr){ return; }
                    /* if ptr->previous == NULL */
                    if(!ptr->previous){ 
                                    if(*ptr->ptr){
                                                free(ptr->ptr);
                                                ptr->ptr = NULL;
                                    }
                                    free(ptr);
                                    ptr = NULL; 
                            return;                 
                    }

                    struct my_ptr_t * tmp = ptr;    
                    for(;tmp != NULL; tmp = tmp->previous ){
                                            if(*tmp->ptr){
                                                        if(**(size_t**)tmp->ptr == tmp->mem){
                                                                                                                                                    free(*tmp->ptr);
                                                                        *tmp->ptr = NULL;
                                                        }
                                            }
                                        free(tmp);
                    } 
            return; 
};

void PtrCheck(void){
                if(!ptr){ return; }
                if(!ptr->previous){
                        if(*(size_t*)ptr->ptr){
                                    if(*ptr->ptr){
                                                if(**(size_t**) ptr->ptr != ptr->mem){
                                                                printf("\tpointer %I64x points not to a valid memory address", ptr->mem);
                                                                printf(" did you freed the memory and not NULL'ed the pointer or used arthmetric's on pointer %I64x?\n", *ptr->ptr);
                                                                return; 
                                                        }   
                                    }
                                    return;
                                }
                        return;
                }
                struct my_ptr_t * tmp = ptr;
                for(;tmp->previous != NULL; tmp = tmp->previous){   
                                if(*(size_t*)tmp->ptr){         
                                                   if(*tmp->ptr){
                                                            if(**(size_t**) tmp->ptr != tmp->mem){
                                                                        printf("\tpointer %I64x points not to a valid memory address", tmp->mem);
                                                                        printf(" did you freed the memory and not NULL'ed the pointer or used arthmetric's on pointer %I64x?\n", *tmp->ptr);                            continue;
                                                            } 
                                                    }
                                                    continue;
                                }

                } 
            return;
       };

 int main(void){
        printf("\n\n\t *************** Test ******************** \n\n");
        size_t i = 0;
        printf("\t *************** create tracker ********************\n");
        int * ptr = malloc(sizeof(int) * 10);
        Ptr(&ptr);
        printf("\t *************** check tracker ********************\n");
        PtrCheck();
        printf("\t *************** free pointer ********************\n");
        free(ptr);
        printf("\t *************** check tracker ********************\n");
        PtrCheck();
        printf("\t *************** set pointer NULL *******************\n");
        ptr = NULL;
        printf("\t *************** check tracker ********************\n");
                PtrCheck();
        printf("\t *************** free tracker ********************\n");
        PtrFree();
        printf("\n\n\t *************** single check done *********** \n\n");
        printf("\n\n\t *************** start multiple test *********** \n");
        int * ptrs[10];
        printf("\t *************** create trackers ********************\n");
        for(; i < 10; i++){
                        ptrs[i] = malloc(sizeof(int) * 10 * i);
                        Ptr(&ptrs[i]);
                 }
        printf("\t *************** check trackers ********************\n");
        PtrCheck();
        printf("\t *************** free pointers but set not NULL *****\n");
        for(i--; i > 0; i-- ){ free(ptrs[i]); }
        printf("\t *************** check trackers ********************\n");
        PtrCheck(); 
        printf("\t *************** set pointers NULL *****************\n");
        for(i=0; i < 10; i++){ ptrs[i] = NULL; }
        printf("\t *************** check trackers ********************\n");
        PtrCheck(); 
        printf("\t *************** free trackers ********************\n");
        PtrFree();
        printf("\tdone");
    return 0;
 }

计算机中几乎从不“永不”。 跨平台远远超出预期。 25年后,我参与了数百个项目,他们都期待跨平台,而且从未实现过。

显然,堆栈上的变量将指向堆栈上的一个区域,该区域几乎是线性的。 跨平台垃圾收集器工作,通过标记堆栈的顶部或(底部),调用一个小函数来检查堆栈是向上还是向下增长,然后检查堆栈指针以了解堆栈的大小。 这是你的范围。 我不知道一台没有以这种方式实现堆栈的机器(无论是成长还是成长)。

您只需检查我们的对象或指针的地址是否位于堆栈的顶部和底部之间。 这就是你如何知道它是否是一个堆栈变量。

太简单。 嘿,这是正确的c ++吗? 没有。重要吗? 在25年里,我看到了更多正确的估计方法。 好吧,让我们这样说吧:如果你是黑客,你没有做真正的编程,你可能只是在重新调整已经完成的事情。

这有趣吗?

好吧,我不知道是否有人没有把它放在这里,或者是否有可能在你的程序中。 我在大学项目中遇到过类似的事情。

我很简单地解决了它 - 在main()的初始化部分,在我声明LIST *ptr ,我只是把那个ptr=NULL 像这样 -

int main(int argc, char **argv) {

LIST *ptr;
ptr=NULL;

因此,当分配失败或者根本没有分配指针时,它将为NULL。 所以你可以用if来测试它。

if (ptr==NULL) {
  "THE LIST DOESN'T EXIST" 
} else {
  "THE LIST MUST EXIST --> SO IT HAS BEEN ALLOCATED"
}

我不知道你的程序是如何编写的,但你肯定明白我想指出的是什么。 如果可以像这样检查你的分配然后将你的参数传递给你的函数,你可以有一个简单的解决方案。

当然,你必须小心你的功能分配和创建结构很好,但在C中你不必小心。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM