wake-up-neo.com

Prüfen, ob einem Zeiger Speicher zugewiesen ist oder nicht

Kann man überprüfen, ob ein an eine Funktion übergebener Zeiger mit Speicher belegt ist oder nicht in C?

Ich habe meine eigene Funktion in C geschrieben, die einen Zeichenzeiger - buf [Zeiger auf einen Puffer] und eine Größe - buf_siz [Puffergröße] akzeptiert. Vor dem Aufruf dieser Funktion muss der Benutzer einen Puffer erstellen und diesem Speicher von buf_siz zuordnen. 

Da die Möglichkeit besteht, dass der Benutzer die Speicherzuordnung vergisst und einfach den Zeiger auf meine Funktion übergibt, möchte ich dies überprüfen. Gibt es eine Möglichkeit, meine Funktion zu überprüfen, um zu sehen, ob der übergebene Zeiger wirklich mit buf_siz Speicher belegt ist?

EDIT1: Es scheint keine Standardbibliothek zu geben, die es überprüfen kann ... aber gibt es einen schmutzigen Hack, um es zu überprüfen .. ??

EDIT2: Ich weiß, dass meine Funktion von einem guten C-Programmierer verwendet wird ... Aber ich möchte wissen, ob wir das überprüfen können oder nicht .. wenn wir können, würde ich es gerne hören.

Fazit: Es ist also nicht möglich zu überprüfen, ob ein bestimmter Zeiger mit Speicher belegt ist oder nicht in einer Funktion

58
codingfreak

Sie können nur einige implementierungsspezifische Hacks überprüfen.

Zeiger haben keine anderen Informationen als den Punkt, auf den sie zeigen. Das Beste, was Sie tun können, ist zu sagen "Ich weiß, wie diese bestimmte Compilerversion Speicher belegt, also werde ich den Speicher dereferenzieren, den Zeiger um 4 Byte zurückschieben, die Größe überprüfen und sicherstellen, dass er passt ..." und so weiter. Sie können dies nicht standardmäßig tun, da die Speicherzuordnung implementierungsdefiniert ist. Ganz zu schweigen davon, dass sie es vielleicht überhaupt nicht dynamisch zugewiesen haben.

Sie müssen lediglich davon ausgehen, dass Ihr Client in C programmieren kann. Die einzige Unlösung, die ich mir vorstellen kann, wäre, den Speicher selbst zuzuordnen und zurückzusenden, aber das ist kaum eine kleine Änderung. (Es ist eine größere Designänderung.)

26
GManNickG

Für eine plattformspezifische Lösung interessieren Sie sich möglicherweise für die Win32-Funktion IsBadReadPtrNAME _ (und andere mögen es). Diese Funktion kann (fast) vorhersagen, ob beim Lesen aus einem bestimmten Speicherblock ein Segmentierungsfehler auftritt.

Dies schützt Sie jedoch nicht im allgemeinen Fall, da das Betriebssystem nichts über den C-Laufzeit-Heap-Manager weiß und wenn ein Aufrufer einen übergibt Wenn der Puffer nicht so groß ist, wie Sie es erwarten, ist der Rest des Heap-Blocks aus Sicht des Betriebssystems weiterhin lesbar.

9
Greg Hewgill

Mit dem folgenden Code habe ich einmal überprüft, ob ein Zeiger versucht, auf illegalen Speicher zuzugreifen. Der Mechanismus soll einen SIGSEGV induzieren. Das SEGV-Signal wurde zuvor zu einer privaten Funktion umgeleitet, die Longjmp verwendet, um zum Programm zurückzukehren. Es ist eine Art Hack, aber es funktioniert.

Der Code kann verbessert werden (verwenden Sie 'sigaction' anstelle von 'signal' usw.), aber er soll nur eine Idee geben. Es ist auch auf andere Unix-Versionen portierbar, für Windows bin ich mir nicht sicher. Beachten Sie, dass das SIGSEGV-Signal an keiner anderen Stelle in Ihrem Programm verwendet werden sollte.

#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);
}
8
Peter

Ich habe einmal einen schmutzigen Hack auf meinem 64-Bit-Solaris verwendet. Im 64-Bit-Modus beginnt der Heap mit 0x1 0000 0000. Durch Vergleichen des Zeigers konnte ich feststellen, ob es sich um einen Zeiger im Daten- oder Codesegment p < (void*)0x100000000, um einen Zeiger im Heap p > (void*)0x100000000 oder um einen Zeiger in einem Speicherabbildungsbereich (intptr_t)p < 0 (mmap gibt Adressen zurück vom oberen Rand des adressierbaren Bereichs) . Dies erlaubte in meinem Programm, zugewiesene und vom Speicher zugeordnete Zeiger in derselben Karte zu halten, und mein Kartenmodul hat die richtigen Zeiger frei.

Diese Art von Trick ist jedoch höchst unporträtierbar. Wenn Ihr Code auf so etwas angewiesen ist, ist es an der Zeit, die Architektur Ihres Codes zu überdenken. Du machst wahrscheinlich etwas falsch.

6

Ich initialisiere Zeiger immer auf den Nullwert. Wenn ich also Speicher zuweise, ändert sich dies. Wenn ich überprüfe, ob Speicher zugewiesen wurde, mache ich pointer != NULL. Wenn ich Speicher freigabe, setze ich den Zeiger auf null. Ich kann mir nicht vorstellen, ob genügend Speicherplatz zugewiesen wurde.

Das löst Ihr Problem zwar nicht, aber Sie müssen darauf vertrauen, dass jemand, der C-Programme schreibt, genug Erfahrung hat, um es richtig zu machen.

4
Yelonek

Ich weiß, dass dies eine alte Frage ist, aber in C ist fast alles möglich. Es gibt bereits einige hackhafte Lösungen, aber um festzustellen, ob der Speicher ordnungsgemäß zugewiesen wurde, kann ein Oracle verwendet werden, um den Platz von malloc, calloc zu übernehmen. , realloc und free. Dies ist die gleiche Art und Weise, wie das Testen von Frameworks (wie z. B. cmocka) Speicherprobleme erkennen kann (seg-Fehler, kein Speicherplatz freigeben usw.). Sie können eine Liste der zugewiesenen Speicheradressen verwalten und diese Liste einfach überprüfen, wenn der Benutzer Ihre Funktion verwenden möchte. Ich habe etwas sehr Ähnliches für mein eigenes Testframework implementiert. Ein Beispielcode:

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;
}

Sie hätten ähnliche Funktionen für calloc, realloc und free, wobei jedem Wrapper der Code __wrap_ vorangestellt ist. Die echte malloc ist durch die Verwendung von __real_malloc verfügbar (ähnlich für die anderen Funktionen, die Sie umschließen). Wenn Sie überprüfen möchten, ob tatsächlich Speicher zugeordnet ist, durchlaufen Sie einfach die verknüpfte memory_ref-Liste und suchen Sie nach der Speicheradresse. Wenn Sie es finden und groß genug sind, wissen Sie sicher, dass die Speicheradresse Ihr Programm nicht zum Absturz bringen wird. Anderenfalls einen Fehler zurückgeben. In der Header-Datei, die Ihr Programm verwendet, würden Sie folgende Zeilen hinzufügen:

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...

Meine Anforderungen waren ziemlich einfach, daher habe ich eine sehr einfache Implementierung implementiert. Sie können sich jedoch vorstellen, wie diese erweitert werden kann, um ein besseres Tracking-System zu erhalten (z. B. Erstellen eines struct, das zusätzlich zur Größe den Speicherort aufzeichnet). Dann kompilieren Sie einfach den Code mit 

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

Der Nachteil besteht darin, dass der Benutzer seinen Quellcode mit den obigen Anweisungen kompilieren muss. Es ist jedoch alles andere als das, was ich je gesehen habe. Das Zuordnen und Freigeben von Speicher ist mit einem gewissen Aufwand verbunden, beim Hinzufügen von Sicherheit ist jedoch immer ein gewisser Aufwand erforderlich.

3
c1moore

Ein Hacker, den Sie versuchen können, besteht darin, zu überprüfen, ob Ihr Zeiger auf den zugewiesenen Speicherstapel zeigt .. Dies hilft Ihnen im Allgemeinen nicht, da der zugewiesene Puffer möglicherweise zu klein ist oder der Zeiger auf einen globalen Speicherabschnitt (.bss, ...).

Um diesen Hack auszuführen, speichern Sie zuerst die Adresse der ersten Variablen in main (). Später können Sie diese Adresse mit der Adresse einer lokalen Variablen in Ihrer spezifischen Routine vergleichen. Alle Adressen zwischen beiden Adressen befinden sich im Stapel.

2
swegi

Nein, im Allgemeinen gibt es keine Möglichkeit, dies zu tun.

Wenn Ihre Schnittstelle nur "einen Zeiger auf einen Puffer weitergibt, in den ich Sachen einfügen werde", kann der Anrufer außerdem nicht wählen, um Speicher zuzuweisen, und stattdessen einen Puffer mit fester Größe verwenden, der statisch zugewiesen ist oder eine automatische Variable oder etwas. Oder es ist vielleicht ein Zeiger auf einen Teil eines größeren Objekts auf dem Heap.

Wenn Ihre Benutzeroberfläche ausdrücklich sagt: "Übergeben Sie einen Zeiger auf den zugewiesenen Speicher (weil ich ihn aufheben werde)", sollten Sie damit rechnen, dass der Aufrufer dies tun wird. Wenn Sie dies nicht tun, können Sie dies nicht zuverlässig erkennen.

2
Greg Hewgill

Ich kenne keine Möglichkeit, dies aus einem Bibliotheksaufruf heraus zu tun, aber unter Linux können Sie /proc/<pid>/numa_maps betrachten. Es werden alle Speicherbereiche angezeigt und in der dritten Spalte wird "Heap" oder "Stack" angezeigt. Sie können sich den Rohzeigerwert ansehen, um zu sehen, wo er sich befindet.

Beispiel:

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

In dem Heap befinden sich also Zeiger, die über 0x01167000 aber unter 0x7f39904d2000 liegen.

1
Mark Lakata

Sie können nichts mit Standard C überprüfen. Auch wenn Ihr Compiler dafür eine Funktion bereitstellen sollte, wäre dies immer noch eine schlechte Idee. Hier ist ein Beispiel warum:

int YourFunc(char * buf, int buf_size);

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

Wie alle anderen sagten, gibt es keine Standardmethode.

Bisher hat noch niemand " Writing Solid Code " von Steve Maguire erwähnt. Obwohl in einigen Quartieren verfasst, enthält das Buch Kapitel zum Thema Speicherverwaltung und erörtert, wie Sie mit Sorgfalt und vollständiger Kontrolle über alle Speicherbelegungen im Programm nach Belieben vorgehen und feststellen können, ob Sie einen Zeiger haben angegeben ist ein gültiger Zeiger auf dynamisch zugewiesenen Speicher. Wenn Sie jedoch die Verwendung von Bibliotheken von Drittanbietern planen, werden Sie feststellen, dass Sie bei wenigen Bibliotheken die Speicherzuweisungsroutinen ändern können, was die Analyse erheblich erschwert. 

1

im Allgemeinen sind lib-Benutzer für die Eingabeüberprüfung und -prüfung verantwortlich. Möglicherweise sehen Sie ASSERT oder etwas im lib-Code, und sie werden nur für die Debugging-Funktion verwendet. Dies ist eine Standardmethode beim Schreiben von C/C++. während so viele Codierer solche lib-Codes sehr sorgfältig prüfen und überprüfen. wirklich "schlechte" Gewohnheiten. Wie in IOP/IOD angegeben, sollten lib-Schnittstellen die Verträge sein und klarstellen, was die lib tun soll und was nicht, was ein lib-Benutzer tun soll und was nicht notwendig sein sollte.

0
Test

Sie können durch Aufruf von malloc_size(my_ptr) in malloc/malloc.h die von malloc für Ihren Zeiger zugewiesene Größe und 0 zurückgeben, wenn der Zeiger nicht zugewiesen wurde. Beachten Sie, dass malloc die Größe eines zugewiesenen Blocks ändert, um sicherzustellen, dass die Variable mit dem restriktivsten Typ von diesem Zeiger dereferenziert werden kann und der Speicher ausgerichtet wird. Wenn Sie also malloc (1) (und malloc (0)) aufrufen, gibt malloc tatsächlich 16 Bytes zurück (auf den meisten Rechnern), da der restriktivste Typ eine Größe von 16 Bytes hat

0
jde-chil

Nein, das kannst du nicht. Sie werden feststellen, dass dies in keiner Funktion der Standardbibliothek oder anderswo möglich ist. Das liegt daran, dass es keinen Standardweg gibt, um es zu sagen. Der aufrufende Code muss lediglich die Verantwortung für die korrekte Verwaltung des Speichers übernehmen.

0
Chuck

Ein Zeigertracker verfolgt und überprüft die Gültigkeit eines Zeigers

verwendungszweck: 

speicher erstellen int * ptr = malloc (sizeof (int) * 10);

addiere die Zeigeradresse zum Tracker Ptr (& ptr);

auf fehlerhafte Zeiger prüfen PtrCheck ();

und geben Sie alle Tracker am Ende Ihres Codes frei

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;
 }
0
Andre

Es gibt einen einfachen Weg, dies zu tun. Wenn Sie einen Zeiger erstellen, schreiben Sie einen Wrapper darum. Zum Beispiel, wenn Ihr Programmierer Ihre Bibliothek zum Erstellen einer Struktur verwendet.

struct struct_type struct_var;

stellen Sie sicher, dass er Speicherplatz mithilfe Ihrer Funktion wie z

struct struct_type struct_var = init_struct_type()

wenn diese struct_var Speicher enthält, der dynamisch zugewiesen wird, z.

wenn die Definition von struct_type war

typedef struct struct_type {
 char *string;
}struct_type;

dann tun Sie dies in Ihrer Funktion init_struct_type (),

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

Auf diese Weise bleibt der Wert NULL, solange er nicht den Temp-> string einem Wert zuordnet. Sie können in den Funktionen, die diese Struktur verwenden, nachsehen, ob die Zeichenfolge NULL ist oder nicht.

Eine weitere Sache: Wenn der Programmierer so schlecht ist, dass er Ihre Funktionen nicht verwendet, sondern direkt auf nicht zugewiesenen Speicher zugreift, verdient er es nicht, Ihre Bibliothek zu verwenden. Stellen Sie einfach sicher, dass Ihre Dokumentation alles enthält.

0
wlan0

Ein nicht initialisierter Zeiger ist genau das - nicht initialisiert. Es kann auf irgendetwas hinweisen oder einfach eine ungültige Adresse sein (d. H. Keine Adresse, die nicht dem physischen oder virtuellen Speicher zugeordnet ist).

Eine praktische Lösung besteht darin, in den Objekten, auf die verwiesen wird, eine Gültigkeitssignatur zu haben. Erstellen Sie einen malloc () - Wrapper, der die angeforderte Blockgröße plus die Größe einer Signaturstruktur zuweist, eine Signaturstruktur am Anfang des Blocks erstellt, den Zeiger jedoch nach der Signatur auf die Position zurückgibt. Sie können dann eine Validierungsfunktion erstellen, die den Zeiger übernimmt, einen negativen Versatz verwendet, um die Gültigkeitsstruktur abzurufen, und diese überprüfen. Sie benötigen natürlich einen entsprechenden free () - Wrapper, um den Block durch Überschreiben der Gültigkeitssignatur für ungültig zu erklären und den freien Start vom zugewiesenen Block auszuführen.

Als Gültigkeitsstruktur können Sie die Größe des Blocks und dessen Ergänzung verwenden. Auf diese Weise haben Sie nicht nur die Möglichkeit, den Block zu validieren (XOR die beiden Werte und den Vergleich mit Null), sondern auch Informationen über die Blockgröße.

0
Clifford