簡體   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