Проверка, выделен ли указатель памяти или нет


можем ли мы проверить, выделен ли указатель, переданный функции с памятью или нет в C?

У меня есть wriiten моя собственная функция в C, которая принимает указатель символа - buf [указатель на буфер] и размер - buf_siz [размер буфера]. Фактически перед вызовом этой функции пользователь должен создать буфер и выделить ему память buf_siz.

Так как есть вероятность, что пользователь может забыть сделать выделение памяти и просто передать указатель на мою функцию я хочу проверить это. Так что есть ли способ, которым я могу проверить свою функцию, чтобы увидеть, действительно ли переданный указатель выделяется с объемом памяти buf_siz .. ??

EDIT1: кажется, что нет стандартной библиотеки, чтобы проверить его .. но есть грязный хак, чтобы проверить его .. ??

EDIT2: Я знаю, что моя функция будет использоваться хороших программистов ... Но я хочу знать, можем ли мы проверить или нет. . если мы можем, я хотел бы услышать это ..

Вывод: Таким образом, невозможно проверить, выделен ли конкретный указатель с памятью или нет в функции

18 57

18 ответов:

вы не можете проверить, за исключением некоторых конкретных хаков реализации.

указатели не имеют никакой информации с ними, кроме того, где они указывают. Лучшее, что вы можете сделать, это сказать: "я знаю, как этой версии компилятор выделяет память, так что я разыменования памяти, переместите указатель 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 возвращает адреса из верхней части адресуемой области). Это позволило в моей программе удерживать выделенные и сопоставленные с памятью указатели на одной карте и освободить мой модуль карты правильно указатели.

но этот вид трюка очень не переносим, и если ваш код полагается на что-то подобное, пришло время переосмыслить архитектуру вашего кода. Вы, наверное, делаете что-то не так.

Я всегда инициализировать указатели значением null. Поэтому, когда я выделяю память, она изменится. Когда я проверяю, была ли выделена память, я делаю pointer != NULL. Когда я освобождаю память, я также устанавливаю указатель на null. Я не могу придумать никакого способа узнать, было ли выделено достаточно памяти.

Это не решает вашу проблему, но вы должны верить, что если кто-то пишет программы на C, то он достаточно квалифицирован, чтобы сделать это правильно.

нет, вообще нет никакого способа сделать это.

кроме того, если ваш интерфейс просто "передать указатель на буфер, где я буду помещать материал", то вызывающий может выбрать не чтобы выделить память вообще, а вместо этого использовать буфер фиксированного размера, который статически выделяется или автоматическая переменная или что-то еще. Или, возможно, это указатель на часть большего объекта в куче.

Если ваш интерфейс специально говорит " передать указатель на выделенный память (потому что я собираюсь освободить его)", то вы должны ожидать, что вызывающий абонент сделает это. Неспособность сделать это не то, что вы можете обнаружить.

один хак вы можете попробовать проверяет, если ваш указатель указывает на стек выделенной памяти. Это не поможет вам в целом, поскольку выделенный буфер может быть небольшим или указатель указывает на некоторый раздел глобальной памяти (.ОНБ. ,константа. ,..).

чтобы выполнить этот хак, вы сначала сохраните адрес первой переменной в main (). Позже, вы можете сравнить этот адрес с адресом локальной переменной в конкретной рутины. Все адреса между обоими адресами расположены на стек.

Я знаю, что это старый вопрос, но почти все возможно в C. Здесь уже есть несколько хакерских решений, но действительный способ определить, правильно ли выделена память, - использовать oracle вместо malloc,calloc,realloc и free. Это тот же способ тестирования фреймворков (например, 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;
}

у вас были бы аналогичные функции для calloc,realloc и free, каждая обертка с префиксом __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

недостатком является то, что пользователь должен скомпилировать свой исходный код с указанными выше директивами; однако, это далеко не худшее, что я видел. Есть некоторые накладные расходы на выделение и освобождение памяти, но всегда есть некоторые накладные расходы при добавлении безопасности.

вы не можете проверить ничего доступного в стандарте 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, расположены в куче.

Вы можете, позвонив malloc_size(my_ptr) на malloc/malloc.h он возвращает размер malloc выделил для вас для вашего указателя и 0, если указатель не был выделен. Имейте в виду, что malloc изменяет размер выделенного блока, чтобы гарантировать, что наиболее ограничительная переменная типа может быть разыменована из этого указателя и выровнять память. Поэтому, если вы вызываете malloc (1) (а также malloc (0)), malloc фактически возвращает 16 байт (на большинстве машин), потому что самый ограничительный тип имеет размер 16 байт

нет, вы не можете. Вы заметите, что нет функции в стандартной библиотеке или где-нибудь еще сделать это. Это потому, что нет стандартного способа сказать. Вызывающий код просто должен взять на себя ответственность за правильное управление памятью.

в целом пользователи lib несут ответственность за проверку и проверку ввода. Вы можете увидеть ASSERT или что-то в коде lib, и они используются только для отладки perpose. это стандартный способ при написании C / C++. в то время как так много кодеров любят делать такую проверку и проверку в своем коде lib очень тщательно. действительно "плохие" привычки. Как указано в IOP/IOD, интерфейсы lib должны быть контрактами и четко указывать, что будет делать lib, а что нет, и что должен делать пользователь lib, а что не должно быть необходимый.

неинициализированный указатель - это именно то, что-неинициализировано. Он может указывать на что угодно или просто быть недопустимым адресом (т. е. не сопоставленным с физической или виртуальной памятью).

практическое решение состоит в том, чтобы иметь подпись валидности в указанных объектах. Создайте оболочку malloc (), которая выделяет запрошенный размер блока плюс размер структуры подписи, создает структуру подписи в начале блока, но возвращает указатель на местоположение после подписи. Затем можно создать функцию проверки, которая принимает указатель, использует отрицательное смещение для получения структуры валидности и проверяет ее. Конечно, вам понадобится соответствующая свободная () оболочка, чтобы аннулировать блок путем перезаписи подписи действительности и выполнить свободный от истинного начала выделенного блока.

в качестве структуры валидности вы можете использовать размер блока и его дополнение. Таким образом, у вас не только есть способ проверки блока (XOR the two значения и сравнить с нулем), но у вас также есть информация о размере блока.

в компьютерах почти никогда нет "никогда". Кросс-платформа намного больше, чем ожидалось. После 25 лет я работал над сотнями проектов, все они ожидали кросс-платформы, и это никогда не материализовалось.

очевидно, что переменная в стеке будет указывать на область в стеке, которая почти линейна. Кросс-платформенные сборщики мусора работают, отмечая верхнюю или (нижнюю) часть стека, вызывая небольшую функцию, чтобы проверить, растет ли стек вверх или вниз, а затем проверка указателя стека, чтобы узнать, насколько велик стек. Это ваш выбор. Я не знаю машины, которая не реализует стек таким образом (либо растет вверх, либо вниз.)

вы просто проверить, если адрес нашего объекта или указателя находится между верхней и нижней части стека. Вот как вы узнаете, является ли это переменной стека.

слишком просто. Эй, это правильно c++? Нет. Правильно ли это важно? В 25 лет я увидел способ более правильной оценки. Что ж, давайте скажем так: если вы взламываете, вы не занимаетесь реальным программированием, вы, вероятно, просто перестраиваете то, что уже было сделано.

насколько это интересно?

есть простой способ сделать это. Всякий раз, когда вы создаете указатель, напишите обертку вокруг него. Например, если ваш программист использует вашу библиотеку для создания структуры.

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->для значения, она останется нулевой. Вы можете проверить функции, которые используют эту структуру, если строка имеет значение NULL или нет.

еще одна вещь, если программист настолько плох, что он не может использовать ваши функции, а скорее напрямую обращается к нераспределенной памяти, он не заслуживает использования вашей библиотеки. Просто убедитесь, что в вашей документации указано все.

указатель трекер, отслеживает и проверяет валидность указателя

использование:

создать память 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;
 }