Peut-on vérifier si un pointeur transmis à une fonction est alloué en mémoire ou non en C?
J'ai écrit ma propre fonction en C qui accepte un pointeur de caractère - buf [pointeur sur un tampon] et size - buf_siz [taille du tampon]. En réalité, avant d'appeler cette fonction, l'utilisateur doit créer un tampon et l'allouer à la mémoire de buf_siz.
Puisqu'il y a une chance pour que l'utilisateur oublie de faire l'allocation de mémoire et passe simplement le pointeur à ma fonction, je veux vérifier ceci. Donc, y a-t-il moyen de vérifier ma fonction pour voir si le pointeur transmis est vraiment alloué avec la quantité de mémoire buf_siz .. ??
EDIT1: Il semble qu'il n'y ait pas de bibliothèque standard pour le vérifier .. mais y a-t-il un bidouillage sale pour le vérifier .. ??
EDIT2: Je sais que ma fonction sera utilisée par un bon programmeur C ... Mais je veux savoir si pouvons-nous vérifier ou non .. si nous le pouvons, j'aimerais l'entendre ..
Conclusion: Il est donc impossible de vérifier si un pointeur particulier est alloué avec de la mémoire ou non dans une fonction
Vous ne pouvez pas vérifier, sauf quelques piratages spécifiques à l’implémentation.
Les pointeurs n'ont aucune information avec eux autre que l'endroit où ils pointent. Le mieux que vous puissiez faire est de dire "Je sais comment cette version du compilateur alloue de la mémoire. Je vais donc déréférencer la mémoire, déplacer le pointeur de 4 octets en arrière, vérifier la taille, vérifier qu'elle correspond ...", etc. Vous ne pouvez pas le faire de manière standard, car l'allocation de mémoire est définie par l'implémentation. Sans parler du fait qu'ils ne l'auraient peut-être pas allée de manière dynamique.
Vous devez simplement supposer que votre client sait programmer en C. La seule solution à laquelle je puisse penser serait d'allouer la mémoire vous-même et de la restituer, mais ce n'est pas un mince changement. (C'est un changement de conception plus important.)
Pour une solution spécifique à la plate-forme, vous pouvez être intéressé par la fonction Win32 IsBadReadPtr
(et d’autres similaires). Cette fonction pourra (presque) prédire si vous obtiendrez une erreur de segmentation lors de la lecture d'un bloc de mémoire particulier.
Cependant, cela ne vous protège pas dans le cas général, car le système d'exploitation ne sait rien du gestionnaire de tas d'exécution C, et si un appelant passe dans une mémoire tampon qui n'est pas aussi Comme vous le souhaitez, le reste du bloc de tas restera lisible du point de vue du système d'exploitation.
Le code ci-dessous correspond à ce que j’ai utilisé une fois pour vérifier si un pointeur tente d’accéder à une mémoire illégale. Le mécanisme consiste à induire un SIGSEGV. Le signal SEGV a été redirigé vers une fonction privée précédemment, qui utilise longjmp pour revenir au programme. C'est un peu un bidouillage mais ça marche.
Le code peut être amélioré (utilisez 'sigaction' au lieu de 'signal' etc.), mais c'est juste pour donner une idée. En outre, il est portable vers d'autres versions d'Unix. Pour Windows, je ne suis pas sûr. Notez que le signal SIGSEGV ne doit pas être utilisé ailleurs dans votre programme.
#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);
}
Une fois, j'ai utilisé un hack sale sur mon Solaris 64 bits. En mode 64 bits, le tas commence à 0x1 0000 0000. En comparant le pointeur, je pouvais déterminer s'il s'agissait d'un pointeur dans le segment de données ou de code p < (void*)0x100000000
, d'un pointeur dans le tas p > (void*)0x100000000
ou d'un pointeur dans une région mappée en mémoire (intptr_t)p < 0
(mmap renvoie des adresses Cela permettait dans mon programme de conserver les pointeurs alloués et mappés en mémoire dans la même carte et de laisser mon module de carte libérer les pointeurs corrects.
Mais ce genre d’astuce est très peu exploitable et si votre code repose sur quelque chose comme ça, il est temps de repenser l’architecture de votre code. Vous faites probablement quelque chose de mal.
J'initialise toujours les pointeurs sur une valeur nulle. Par conséquent, lorsque j'alloue de la mémoire, cela va changer. Quand je vérifie si la mémoire a été allouée, je fais pointer != NULL
. Lorsque je libère de la mémoire, je règle également le pointeur sur null. Je ne vois aucun moyen de savoir s'il y avait suffisamment de mémoire allouée.
Cela ne résout pas votre problème, mais vous devez être convaincu que si quelqu'un écrit des programmes en C, il est suffisamment habile pour le faire correctement.
Je sais que c’est une vieille question, mais que presque tout est possible en C. Il existe déjà quelques solutions compliquées, mais un moyen valable de déterminer si la mémoire a été correctement allouée est d’utiliser un Oracle pour remplacer malloc
, calloc
, realloc
, et free
. C'est de la même manière que les frameworks de test (tels que cmocka) peuvent détecter des problèmes de mémoire (erreurs de segmentation, non libération de mémoire, etc.). Vous pouvez gérer une liste d'adresses mémoire allouées au fur et à mesure de leur affectation et simplement vérifier cette liste lorsque l'utilisateur souhaite utiliser votre fonction. J'ai implémenté quelque chose de très similaire pour mon propre framework de test. Quelques exemples de code:
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;
}
Vous auriez des fonctions similaires pour calloc
, realloc
et free
, chaque encapsuleur ayant le préfixe __wrap_
. La vraie malloc
est disponible grâce à l'utilisation de __real_malloc
(similaire pour les autres fonctions que vous habillez). Chaque fois que vous souhaitez vérifier si la mémoire est réellement allouée, il vous suffit de parcourir la liste memory_ref
liée et de rechercher l'adresse de la mémoire. Si vous le trouvez et qu'il est assez grand, vous savez avec certitude que l'adresse mémoire ne plantera pas votre programme; sinon, retourne une erreur. Dans le fichier d'en-tête utilisé par votre programme, vous ajouterez ces lignes:
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...
Mes besoins étaient assez simples, j'ai donc implémenté une implémentation très basique, mais vous pouvez imaginer comment cela pourrait être étendu pour avoir un meilleur système de suivi (par exemple, créer une struct
qui garde une trace de l'emplacement de la mémoire en plus de la taille). Ensuite, vous compilez simplement le code avec
gcc src_files -o dest_file -Wl,-wrap,malloc -Wl,-wrap,calloc -Wl,-wrap,realloc -Wl,-wrap,free
L'inconvénient est que l'utilisateur doit compiler son code source avec les directives ci-dessus; Cependant, c'est loin d'être le pire que j'ai vu. L'allocation et la libération de mémoire entraînent des frais supplémentaires, mais il y a toujours des frais supplémentaires lorsque vous ajoutez de la sécurité.
Un hack que vous pouvez essayer est de vérifier si votre pointeur veut empiler de la mémoire allouée . Cela ne vous aidera généralement pas, car le tampon alloué peut être trop petit ou le pointeur pointe vers une section de mémoire globale (.bss, .const, ...).
Pour effectuer ce hack, vous stockez d'abord l'adresse de la première variable dans main (). Plus tard, vous pourrez comparer cette adresse à l'adresse d'une variable locale de votre routine spécifique . Toutes les adresses situées entre les deux adresses sont situées dans la pile.
Non, en général, il n'y a aucun moyen de faire cela.
En outre, si votre interface consiste simplement à "transmettre un pointeur à un tampon dans lequel je mettrai des éléments", l'appelant peut choisir non d'allouer de la mémoire, et utiliser à la place un tampon de taille fixe, alloué de manière statique ou automatique. variable ou quelque chose. Ou peut-être que c'est un pointeur sur une partie d'un objet plus grand sur le tas.
Si votre interface indique spécifiquement "passez un pointeur sur la mémoire allouée (car je vais le désallouer)", attendez-vous à ce que l'appelant le fasse. Ne pas le faire n'est pas quelque chose que vous pouvez détecter de manière fiable.
Je ne connais pas le moyen de le faire à partir d'un appel de bibliothèque, mais sous Linux, vous pouvez regarder /proc/<pid>/numa_maps
. Il affichera toutes les sections de la mémoire et la troisième colonne indiquera "tas" ou "pile". Vous pouvez regarder la valeur du pointeur brut pour voir où il s’aligne.
Exemple:
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
Les pointeurs situés au-dessus de 0x01167000 mais inférieur à 0x7f39904d2000 sont donc situés dans le tas.
Vous ne pouvez vérifier avec rien de ce qui est disponible en standard C. Même si votre compilateur spécifique devait fournir une fonction pour le faire, ce serait quand même une mauvaise idée. Voici un exemple de pourquoi:
int YourFunc(char * buf, int buf_size);
char str[COUNT];
result = YourFunc(str, COUNT);
Comme tout le monde l'a dit, il n'y a pas de méthode standard pour le faire.
Jusqu'à présent, personne d'autre n'a mentionné ' Writing Solid Code ' de Steve Maguire. Bien que critiqué dans quelques trimestres , le livre contient des chapitres sur la gestion de la mémoire et explique comment, avec soin et contrôle total de l’allocation de mémoire du programme, vous pouvez faire ce que vous demandez et déterminer sont donnés est un pointeur valide sur la mémoire allouée dynamiquement. Toutefois, si vous envisagez d’utiliser des bibliothèques tierces, vous constaterez que peu d’entre elles vous permettent de modifier les routines d’allocation de mémoire selon vos propres critères, ce qui complique grandement une telle analyse.
en général, les utilisateurs de la bibliothèque sont responsables du contrôle et de la vérification des entrées. Vous pouvez voir ASSERT ou quelque chose dans le code de la bibliothèque et ils sont utilisés uniquement à des fins de débogage. C’est une manière standard d’écrire en C/C++. alors que tant de codeurs aiment faire de telles vérifications et vérification de leurs codes de lib très soigneusement. vraiment "mauvaises" habitudes. Comme indiqué dans IOP/IOD, les interfaces de bibliothèque doivent être les contrats et indiquer clairement ce que la bibliothèque fera et ce qu’elle ne fera pas, et ce qu’un utilisateur de bibliothèque devrait faire et ce qui ne devrait pas être nécessaire.
Vous pouvez appeler malloc_size(my_ptr)
dans malloc/malloc.h
pour obtenir la taille que malloc vous a attribuée pour votre pointeur et 0 si le pointeur n’a pas été alloué. N'oubliez pas que malloc redimensionne la taille d'un bloc alloué pour s'assurer que la variable de type la plus restrictive puisse être déréférencée à partir de ce pointeur et pour aligner la mémoire. Donc, si vous appelez malloc (1) (ainsi que malloc (0)), malloc renvoie effectivement 16 octets (sur la plupart des machines) car le type le plus restrictif a une taille de 16 octets.
Non, tu ne peux pas. Vous remarquerez qu'aucune fonction de la bibliothèque standard ou ailleurs ne fait cela. C'est parce qu'il n'y a pas de moyen standard de le savoir. Le code d'appel doit simplement accepter la responsabilité de gérer correctement la mémoire.
Un traqueur de pointeur, suit et vérifie la validité d'un pointeur
usage:
créer une mémoire int * ptr = malloc (sizeof (int) * 10);
ajoutez l'adresse du pointeur au suivi Ptr (& ptr);
recherchez les pointeurs défaillants PtrCheck ();
et libérer tous les trackers à la fin de votre code
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;
}
Il existe un moyen simple de le faire. Chaque fois que vous créez un pointeur, écrivez un wrapper autour de celui-ci. Par exemple, si votre programmeur utilise votre bibliothèque pour créer une structure.
struct struct_type struct_var;
assurez-vous qu'il alloue de la mémoire en utilisant votre fonction telle que
struct struct_type struct_var = init_struct_type()
si cette struct_var contient de la mémoire allouée dynamiquement, par exemple,
si la définition de struct_type était
typedef struct struct_type {
char *string;
}struct_type;
puis dans votre fonction init_struct_type (), faites ceci,
init_struct_type()
{
struct struct_type *temp = (struct struct_type*)malloc(sizeof(struct_type));
temp->string = NULL;
return temp;
}
De cette façon, à moins qu'il n'affecte la chaîne temp-> à une valeur, celle-ci restera NULL. Vous pouvez vérifier les fonctions qui utilisent cette structure, si la chaîne est NULL ou non.
Une dernière chose, si le programmeur est si mauvais, qu’il n’utilise pas vos fonctions mais qu’il accède directement à la mémoire non allouée, il ne mérite pas d’utiliser votre bibliothèque. Assurez-vous simplement que votre documentation spécifie tout.
Un pointeur non initialisé est exactement cela - non initialisé. Cela peut indiquer n'importe quoi ou simplement être une adresse invalide (c'est-à-dire une adresse non mappée à la mémoire physique ou virtuelle).
Une solution pratique consiste à avoir une signature de validité dans les objets pointés. Créez un wrapper malloc () qui alloue la taille de bloc demandée ainsi que la taille d'une structure de signature, crée une structure de signature au début du bloc mais renvoie le pointeur sur l'emplacement situé après la signature. Vous pouvez ensuite créer une fonction de validation qui prend le pointeur, utilise un décalage négatif pour obtenir la structure de validité et la vérifie. Vous aurez bien sûr besoin d'un wrapper free () correspondant pour invalider le bloc en écrasant la signature de validité et pour effectuer la suppression du début réel du bloc alloué.
En tant que structure de validité, vous pouvez utiliser la taille du bloc et son complément. De cette façon, vous disposez non seulement d'un moyen de valider le bloc (XOR les deux valeurs et la comparer à zéro), mais vous avez également des informations sur la taille du bloc.