Подтвердить что ты не робот

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

Я знаю, что это старый вопрос, но почти все возможно в 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

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

Ответ 9

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

int YourFunc(char * buf, int buf_size);

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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

Ответ 13

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

Ответ 14

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

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

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

Ответ 15

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

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

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

Ответ 16

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

Ответ 17

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

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

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

Ответ 18

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

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

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

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

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

Ответ 19

Ну, я не знаю, если кто-то не разместил это здесь или это будет возможно в вашей программе. Я боролся с подобной вещью в своем университетском проекте.

Я решил это довольно просто - в части инициализации main(), после того, как я объявил LIST *ptr, я просто положил это ptr=NULL. Как это -

int main(int argc, char **argv) {

LIST *ptr;
ptr=NULL;

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

if (ptr==NULL) {
  "THE LIST DOESN'T EXIST" 
} else {
  "THE LIST MUST EXIST --> SO IT HAS BEEN ALLOCATED"
}

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

Конечно, вы должны быть осторожны, чтобы ваши функции по распределению и созданию структуры были выполнены хорошо, но в C вам не нужно быть осторожным.