2009-10-16 7 views
53

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

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

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

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

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

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

+2

На самом деле я так не думаю, но я не чувствую себя достаточно уверенно, чтобы опубликовать его в качестве ответа. – Ibrahim

+0

Невозможно проверить, если вы не используете диспетчер памяти или не сворачиваете свой собственный. –

+0

Если его указатель на символ, мы можем сделать strlen() или sizeof() и проверить, сколько памяти выделено (конечно, если строка NULL завершена). Для других типов я не уверен, есть ли какой-то способ. !! –

ответ

25

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

Указатели не имеют никакой информации с ними, кроме тех случаев, когда они указывают. Лучшее, что вы можете сделать, это сказать: «Я знаю, как эта конкретная версия компилятора выделяет память, поэтому я буду разыскивать память, переместить указатель назад на 4 байта, проверить размер, убедиться, что он соответствует ...» и так далее. Вы не можете сделать это стандартным образом, так как выделение памяти определяется реализацией. Не говоря уже о том, что они, возможно, не динамически выделяли его вообще.

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

+0

Указатель может быть не нулевым, но все еще не содержать байтов buf_siz. Я не думаю, что есть какой-то способ проверить, чего хочет ассер. – Ibrahim

+0

Упс, я ускользнул - прочитал вопрос. – GManNickG

+0

Хорошо, как насчет этого? Поскольку это C, клиент, вероятно, использовал 'malloc', который возвращает указатель' NULL', если он не мог выделить память. Итак ... в 'malloc' мы доверяем? – Jacob

1

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

int YourFunc(char * buf, int buf_size); 

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

@Mark - В коде вы назначаете str как массив размера COUNT .. поэтому в 'YourFunc' я все еще могу выполнять операции типа strcpy в размере buf_size. Но если str - это просто указатель на char, тогда попытка выполнить любую операцию strcpy с размером buf_size приведет к «сбою сегментации» – codingfreak

+2

Это ОЧЕНЬ ОЧЕНЬ неправильно, codingfreak. Ошибка сегментации возникает, если «str» - это указатель на символ, указывающий на память, к которой у вас нет доступа.Это не происходит, потому что «str» - это указатель на символ, это происходит потому, что вы просите программу сделать что-то, что ей не разрешено делать. – gnud

+1

@gnud - Извините, я думаю, я ответил неправильно. – codingfreak

0

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

+0

@ Чак, если нет стандартной библиотечной функции, чтобы проверить, есть ли другой выход.? – codingfreak

2

Нет, в общем, нет способа сделать это.

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

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

8

Для решения конкретной платформы вы можете быть заинтересованы в функции Win32 IsBadReadPtr (и тому подобное). Эта функция сможет (почти) предсказать, будет ли возникать ошибка сегментации при чтении из определенного фрагмента памяти.

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

+0

@Greg - Извините, что меня не интересуют функции WIN32 .. если возможно, хорошо работает грязный взломать тоже нормально, так как нет никакой функции стандарта С – codingfreak

+1

Хорошо, вы не указали, какую платформу вы * * заинтересованы в Задание платформы и компилятора может дать вам более конкретный ответ. –

+0

@Greg - Да, я согласен с вами ... – codingfreak

4

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

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

+0

@Yelonek .. Я согласен с вами, но я действительно хочу знать, есть ли возможность проверить .... – codingfreak

+0

Я тоже, но (особенно в библиотеках) s *** происходит. –

1

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

До сих пор никто не упомянул «Writing Solid Code» от Steve Maguire. Несмотря на то, что в некоторых версиях quarters было написано исправление, в книге есть главы, посвященные управлению памятью, и обсуждается, как с осторожностью и полным контролем над распределением памяти в программе вы можете делать, как вы просите, и определить, является ли указатель, который вы даете, действительный указатель на динамически выделенную память. Однако, если вы планируете использовать сторонние библиотеки, вы обнаружите, что немногие из них позволяют вам изменить процедуры распределения памяти на свои, что значительно усложняет такой анализ.

+0

@ Джонатан - Что вы имеете в виду сторонние библиотеки - ?? Я просто использую стандартные библиотеки и ISO C99. Но я просто попробую книгу, которую вы рекомендовали. – codingfreak

+0

Сторонние библиотеки - это все, что вы не писали, включая стандартные библиотеки. Грубо говоря, если он использует malloc() в любом месте, вам будет трудно заменить эти вызовы собственным распределителем памяти, что означает, что будет трудно отслеживать злоупотребления. Возможно, вам придется искать более сложные материалы для отслеживания памяти - проверьте отладочные версии malloc, valgrind, Purify и т. Д. (Это проклятие моей жизни - мы не можем использовать большинство библиотек извне без тяжелой работы, потому что продукт I работа над имеет мучительные требования к управлению памятью, которые библиотеки не знают и не заботятся.) –

2

Один взлом вы можете попробовать проверить, указывает ли ваш указатель на стек выделенной памяти. Это не поможет вам вообще, поскольку выделенный буфер может быть малым или указатель указывает на какой-либо раздел глобальной памяти (.bss, .const, ...).

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

+0

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

+0

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

0

В целом пользователи lib несут ответственность за проверку ввода и проверку. Вы можете увидеть ASSERT или что-то в коде lib, и они используются только для отладки. это стандартный способ написания C/C++. в то время как многие кодеры любят делать такую ​​проверку и верфи в своем коде lib очень осторожно. действительно «ПЛОХОЙ» привычки. Как указано в IOP/IOD, интерфейсы lib должны заключаться в контрактах и ​​разъяснять, что сделает lib, а что нет, и что должен делать пользователь lib и что не нужно.

0

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

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

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

+0

Возможно, вы захотите проверить свое первое предложение: «Инициализированный указатель именно это - неинициализирован». –

+0

Спасибо Крису - исправлено. – Clifford

0

В компьютерах почти никогда не «никогда». Кросс-платформа - это путь, ожидаемый. Через 25 лет я работал над сотнями проектов, которые все ожидали кросс-платформу, и она никогда не материализовалась.

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

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

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

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

+1

Исходный вопрос был о C, а не C++, не упоминал и не подразумевал переменные на стеке, и не был посвящен интересным/новым/уникальным материалам. –

+0

Кроме того, функции 'malloc'-alike не обязательно выполняют действие, которое закончится расширением кучи. C++ имеет совершенно новый способ выделения памяти, и все знают, что использование предварительно определенных функций C не является хорошей идеей. – Malina

0

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

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 или нет.

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

6

Я когда-то использовал грязный хак на своей 64-битной Solaris. В 64-битном режиме куча начинается с 0x1 0000 0000. Сравнивая указатель, я мог бы определить, был ли он указателем в сегменте данных или кода p < (void*)0x100000000, указателем в куче p > (void*)0x100000000 или указателем в области отображаемой памяти (intptr_t)p < 0 (mmap возвращает адреса от вершины адресуемой области). Это позволило в моей программе удерживать выделенные и отображенные на карте указатели на одной карте, а также иметь мой модуль карты, чтобы получить правильные указатели.

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

8

Ниже приведен код, который я использовал один раз, чтобы проверить, пытается ли какой-либо указатель получить доступ к незаконной памяти. Механизм состоит в том, чтобы побудить 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); 
} 
+0

@Saco 'i = malloc (1);' является допустимым кодом C и предпочтительнее 'i = (int *) malloc (1);'. Возможно, вы думаете о другом языке. – chux

1

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

2

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

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

1

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

0

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

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

создать память Int * PTR = таНос (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; 
} 
Смежные вопросы