Есть ли способ определить (конечно, программно), является ли данный указатель «действительным»? Проверить значение NULL просто, но как насчет таких вещей, как 0x00001234? При попытке разыменовать указатель такого типа возникает исключение / сбой.
Кроссплатформенный метод предпочтительнее, но подойдет и платформенный (для Windows и Linux).
Обновление для пояснения: проблема не в устаревших / освобожденных / неинициализированных указателях; вместо этого я реализую API, который принимает указатели от вызывающего (например, указатель на строку, дескриптор файла и т. д.). Вызывающий может отправить (намеренно или по ошибке) недопустимое значение в качестве указателя. Как предотвратить сбой?
Ответы:
Вы не можете сделать этот чек. Проверить, является ли указатель «действительным», просто невозможно. Вы должны верить, что когда люди используют функцию, которая принимает указатель, эти люди знают, что делают. Если они передадут вам 0x4211 в качестве значения указателя, то вы должны доверять ему, указывая на адрес 0x4211. И если они «случайно» столкнутся с объектом, то даже если вы воспользуетесь какой-нибудь пугающей функцией операционной системы (IsValidPtr или что-то еще), вы все равно попадете в ошибку и не выйдете из строя быстро.
Начните использовать нулевые указатели для сигнализации такого рода и скажите пользователю вашей библиотеки, что они не должны использовать указатели, если они склонны случайно передавать неверные указатели, серьезно :)
источник
Вот три простых способа, с помощью которых программа на C под Linux может получить самоанализ о состоянии памяти, в которой она запущена, и о том, почему в некоторых контекстах на этот вопрос есть соответствующие сложные ответы.
В Microsoft Windows есть функция QueryWorkingSetEx, которая задокументирована в API статуса процесса (также в NUMA API). Как следствие сложного программирования NUMA API, эта функция также позволит вам выполнять простую работу по «проверке указателей на достоверность (C / C ++)», поэтому вряд ли она устареет как минимум через 15 лет.
источник
Предотвращение сбоя, вызванного отправкой вызывающим абонентом недопустимого указателя, - хороший способ скрыть ошибки, которые трудно найти.
Не лучше ли для программиста, использующего ваш API, получить четкое сообщение о том, что его код является подделкой, разбив его, а не скрывая?
источник
В Win32 / 64 есть способ сделать это. Попытка прочитать указатель и поймать результирующую исключительную ситуацию SEH, которая будет выдана в случае ошибки. Если не бросает, значит, это действительный указатель.
Проблема с этим методом заключается в том, что он просто возвращает, можете ли вы читать данные из указателя. Это не дает никаких гарантий относительно безопасности типов или любого количества других инвариантов. В общем, этот метод хорош для чего-то другого, кроме как сказать «да, я могу прочитать это конкретное место в памяти в то время, которое уже прошло».
Короче, не делайте этого;)
У Раймонда Чена есть сообщение в блоге на эту тему: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
источник
AFAIK нет никакого способа. Вы должны попытаться избежать этой ситуации, всегда устанавливая указатели на NULL после освобождения памяти.
источник
Взгляните на этот и этот вопрос. Также обратите внимание на умные указатели .
источник
Относительно ответа немного выше в этой теме:
Мой совет - держитесь от них подальше, это кто-то уже опубликовал: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
Еще одно сообщение по той же теме и того же автора (я думаю) это: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx ("IsBadXxxPtr действительно следует называть CrashProgramRandomly ").
Если пользователи вашего API отправляют неверные данные, пусть произойдет сбой. Если проблема заключается в том, что переданные данные не используются позже (и это затрудняет поиск причины), добавьте режим отладки, в котором строки и т. Д. Регистрируются при входе. Если они плохие, это будет очевидно (и, вероятно, выйдет из строя). Если это происходит слишком часто, возможно, стоит вывести ваш API из процесса и позволить им вывести из строя процесс API вместо основного процесса.
источник
Во-первых, я не вижу смысла пытаться защитить себя от вызывающего, сознательно пытающегося вызвать сбой. Они могли легко сделать это, пытаясь получить доступ через недействительный указатель. Есть много других способов - они могут просто перезаписать вашу память или стек. Если вам нужно защитить себя от такого рода вещей, вам нужно запустить отдельный процесс, используя сокеты или какой-либо другой IPC для связи.
Мы пишем довольно много программного обеспечения, которое позволяет партнерам / клиентам / пользователям расширять функциональность. Обо всех ошибках неизбежно сообщается нам первыми, поэтому полезно иметь возможность легко показать, что проблема заключается в коде подключаемого модуля. Кроме того, существуют проблемы с безопасностью, и некоторым пользователям доверяют больше, чем другим.
Мы используем несколько различных методов в зависимости от требований к производительности / пропускной способности и надежности. Из наиболее предпочтительных:
разделять процессы с помощью сокетов (часто передавая данные в виде текста).
отдельные процессы с использованием общей памяти (если нужно передать большие объемы данных).
тот же процесс разделяет потоки через очередь сообщений (при частых коротких сообщениях).
один и тот же процесс разделяет потоки, все передаваемые данные выделяются из пула памяти.
тот же процесс через прямой вызов процедуры - все переданные данные выделяются из пула памяти.
Мы стараемся никогда не прибегать к тому, что вы пытаетесь сделать, когда имеете дело со сторонним программным обеспечением, особенно когда нам предоставляются плагины / библиотека в виде двоичного кода, а не исходного кода.
В большинстве случаев использование пула памяти довольно просто и не обязательно должно быть неэффективным. Если ВЫ выделяете данные в первую очередь, тогда просто проверять указатели на значения, которые вы выделили. Вы также можете сохранить выделенную длину и добавить «магические» значения до и после данных, чтобы проверить допустимый тип данных и переполнение данных.
источник
В Unix вы должны иметь возможность использовать системный вызов ядра, который выполняет проверку указателя и возвращает EFAULT, например:
#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <stdbool.h> bool isPointerBad( void * p ) { int fh = open( p, 0, 0 ); int e = errno; if ( -1 == fh && e == EFAULT ) { printf( "bad pointer: %p\n", p ); return true; } else if ( fh != -1 ) { close( fh ); } printf( "good pointer: %p\n", p ); return false; } int main() { int good = 4; isPointerBad( (void *)3 ); isPointerBad( &good ); isPointerBad( "/tmp/blah" ); return 0; }
возвращение:
bad pointer: 0x3 good pointer: 0x7fff375fd49c good pointer: 0x400793
Вероятно, существует лучший системный вызов, чем open () [возможно, доступ], поскольку есть вероятность, что это может привести к фактическому кодовому пути создания файла и последующему требованию закрытия.
источник
Мне очень симпатичен ваш вопрос, так как я сам нахожусь в почти идентичном положении. Я ценю то, о чем говорится во многих ответах, и они верны - подпрограмма, предоставляющая указатель, должна предоставлять действительный указатель. В моем случае, это почти немыслимо , чтобы они могли быть повреждены указатель - но если они уже удалось, это было бы мое программное обеспечение , которое падает, и ME , что бы получить вину :-(
Мое требование не состоит в том, чтобы я продолжал после ошибки сегментации - это было бы опасно - я просто хочу сообщить о том, что произошло клиенту, прежде чем завершить работу, чтобы они могли исправить свой код, а не обвинять меня!
Вот как я это сделал (в Windows): http://www.cplusplus.com/reference/clibrary/csignal/signal/
Чтобы дать синопсис:
#include <signal.h> using namespace std; void terminate(int param) /// Function executed if a segmentation fault is encountered during the cast to an instance. { cerr << "\nThe function received a corrupted reference - please check the user-supplied dll.\n"; cerr << "Terminating program...\n"; exit(1); } ... void MyFunction() { void (*previous_sigsegv_function)(int); previous_sigsegv_function = signal(SIGSEGV, terminate); <-- insert risky stuff here --> signal(SIGSEGV, previous_sigsegv_function); }
Теперь похоже, что это ведет себя так, как я надеялся (он печатает сообщение об ошибке, а затем завершает программу), но если кто-то может обнаружить ошибку, сообщите мне!
источник
exit()
, он обходит RAII и может вызвать утечку ресурсов.exit()
и мой Portable C ++ Alarm Bell начал звонить. Это должно быть нормально в этой конкретной ситуации Linux, когда ваша программа все равно завершится, извините за шум.man 2 signal
в Linux есть параграф, объясняющий почему.В C ++ нет условий для проверки действительности указателя в общем случае. Очевидно, можно предположить, что NULL (0x00000000) - это плохо, и различные компиляторы и библиотеки любят использовать здесь и там «специальные значения», чтобы упростить отладку (например, если я когда-нибудь увижу указатель, отображаемый как 0xCECECECE в Visual Studio, я знаю Я сделал что-то не так), но правда в том, что, поскольку указатель - это просто индекс в памяти, практически невозможно определить, просто взглянув на указатель, является ли он «правильным» индексом.
Существуют различные уловки, которые вы можете использовать с dynamic_cast и RTTI, например, чтобы гарантировать, что объект, на который указывается, имеет тот тип, который вам нужен, но все они требуют, чтобы вы указывали на что-то действительное в первую очередь.
Если вы хотите убедиться, что ваша программа может обнаруживать «недействительные» указатели, мой совет таков: установите для каждого указателя, который вы объявляете, значение NULL или действительный адрес сразу после создания и установите его в значение NULL сразу после освобождения памяти, на которую он указывает. Если вы усердно относитесь к этой практике, то проверка на NULL - это все, что вам когда-либо понадобится.
источник
Нет никакого переносимого способа сделать это, и сделать это для конкретных платформ может быть сложно или невозможно. В любом случае вам никогда не следует писать код, который зависит от такой проверки - не позволяйте указателям принимать недопустимые значения в первую очередь.
источник
Установка указателя на NULL до и после использования - хороший прием. Это легко сделать в C ++, если вы управляете указателями внутри класса, например (строка):
class SomeClass { public: SomeClass(); ~SomeClass(); void SetText( const char *text); char *GetText() const { return MyText; } void Clear(); private: char * MyText; }; SomeClass::SomeClass() { MyText = NULL; } SomeClass::~SomeClass() { Clear(); } void SomeClass::Clear() { if (MyText) free( MyText); MyText = NULL; } void SomeClass::Settext( const char *text) { Clear(); MyText = malloc( strlen(text)); if (MyText) strcpy( MyText, text); }
источник
Принимать произвольные указатели в качестве входных параметров в общедоступном API - не очень хорошая политика. Лучше иметь типы «простых данных», такие как целое число, строка или структура (я, конечно, имею в виду классическую структуру с простыми данными внутри; официально структурой может быть что угодно).
Зачем? Потому что, как говорят другие, нет стандартного способа узнать, был ли вам дан действительный указатель или тот, который указывает на мусор.
Но иногда у вас нет выбора - ваш API должен принимать указатель.
В этих случаях обязанность вызывающего - передать хороший указатель. NULL может быть принят как значение, но не указатель на мусор.
Можете ли вы как-нибудь перепроверить? Что ж, в таком случае я определил инвариант для типа, на который указывает указатель, и вызвал его, когда вы его получите (в режиме отладки). По крайней мере, если инвариант дает сбой (или дает сбой), вы знаете, что вам передали неверное значение.
// API that does not allow NULL void PublicApiFunction1(Person* in_person) { assert(in_person != NULL); assert(in_person->Invariant()); // Actual code... } // API that allows NULL void PublicApiFunction2(Person* in_person) { assert(in_person == NULL || in_person->Invariant()); // Actual code (must keep in mind that in_person may be NULL) }
источник
Как говорили другие, вы не можете надежно обнаружить недопустимый указатель. Рассмотрим некоторые формы, которые может принимать недопустимый указатель:
У вас может быть нулевой указатель. Это то, что вы можете легко проверить и с этим что-нибудь сделать.
У вас может быть указатель на что-то за пределами допустимой памяти. Что составляет действительную память, зависит от того, как среда выполнения вашей системы устанавливает адресное пространство. В системах Unix это обычно виртуальное адресное пространство, начинающееся с 0 и достигающее некоторого большого количества мегабайт. Во встроенных системах он может быть довольно маленьким. В любом случае он может не начинаться с 0. Если ваше приложение работает в режиме супервизора или в аналогичном режиме, тогда ваш указатель может ссылаться на реальный адрес, который может или не может быть скопирован с реальной памятью.
У вас может быть указатель где-то внутри вашей допустимой памяти, даже внутри сегмента данных, bss, стека или кучи, но не указывающий на действительный объект. Вариант этого - указатель, который указывал на действительный объект до того, как с ним случилось что-то плохое. Плохие вещи в этом контексте включают освобождение, повреждение памяти или повреждение указателя.
У вас может быть явный недопустимый указатель, например указатель с недопустимым выравниванием для объекта, на который ссылаются.
Проблема усугубляется, когда вы рассматриваете архитектуры на основе сегментов / смещений и другие реализации нечетных указателей. Подобные вещи обычно скрыты от разработчика хорошими компиляторами и разумным использованием типов, но если вы хотите пробить завесу и попытаться перехитрить разработчиков операционной системы и компилятора, что ж, вы можете, но нет единого универсального способа чтобы решить все проблемы, с которыми вы можете столкнуться.
Лучшее, что вы можете сделать, - это допустить сбой и выдать хорошую диагностическую информацию.
источник
Эта статья MEM10-C. Определение и использование функции проверки указателя говорит о том, что в некоторой степени можно выполнить проверку, особенно в ОС Linux.
источник
В общем, сделать невозможно. Вот один особенно неприятный случай:
struct Point2d { int x; int y; }; struct Point3d { int x; int y; int z; }; void dump(Point3 *p) { printf("[%d %d %d]\n", p->x, p->y, p->z); } Point2d points[2] = { {0, 1}, {2, 3} }; Point3d *p3 = reinterpret_cast<Point3d *>(&points[0]); dump(p3);
На многих платформах это будет распечатывать:
[0 1 2]
Вы заставляете исполняющую систему неправильно интерпретировать биты памяти, но в этом случае она не выйдет из строя, потому что все биты имеют смысл. Это является частью конструкции языка (взгляд на C-стиле полиморфизма с
struct inaddr
,inaddr_in
,inaddr_in6
), так что вы не можете надежно защитить от него на любой платформе.источник
Невероятно, сколько вводящей в заблуждение информации вы можете прочитать в статьях выше ...
И даже в документации Microsoft msdn IsBadPtr заявлено, что он заблокирован. Ну да ладно, я предпочитаю работающее приложение, а не сбой. Даже если срок работы может работать некорректно (до тех пор, пока конечный пользователь может продолжать работу с приложением).
Погуглил, я не нашел полезного примера для Windows - нашел решение для 32-битных приложений,
http://www.codeproject.com/script/Content/ViewAssociatedFile.aspx?rzp=%2FKB%2Fsystem%2Fdetect-driver%2F%2FDetectDriverSrc.zip&zep=DetectDriverSrc%2FDetect%v&rt&rt&obl=deppsdrc_ddc&rt&hl=ru&obddd_db_db_ddc&b_db_db_db_db_d_d_db_d_db_d_d_dd_d_d_dd_d_d_d_d_d_d_d_d_d_d_d_db_d_b_d_0_db_d_b_d_db_d_d_d_d_d_ddd = 2
но мне также нужна поддержка 64-битных приложений, поэтому это решение мне не подошло.
Но я собрал исходные коды Wine и сумел создать аналогичный код, который будет работать и для 64-битных приложений - прикрепив код сюда:
#include <typeinfo.h> typedef void (*v_table_ptr)(); typedef struct _cpp_object { v_table_ptr* vtable; } cpp_object; #ifndef _WIN64 typedef struct _rtti_object_locator { unsigned int signature; int base_class_offset; unsigned int flags; const type_info *type_descriptor; //const rtti_object_hierarchy *type_hierarchy; } rtti_object_locator; #else typedef struct { unsigned int signature; int base_class_offset; unsigned int flags; unsigned int type_descriptor; unsigned int type_hierarchy; unsigned int object_locator; } rtti_object_locator; #endif /* Get type info from an object (internal) */ static const rtti_object_locator* RTTI_GetObjectLocator(void* inptr) { cpp_object* cppobj = (cpp_object*) inptr; const rtti_object_locator* obj_locator = 0; if (!IsBadReadPtr(cppobj, sizeof(void*)) && !IsBadReadPtr(cppobj->vtable - 1, sizeof(void*)) && !IsBadReadPtr((void*)cppobj->vtable[-1], sizeof(rtti_object_locator))) { obj_locator = (rtti_object_locator*) cppobj->vtable[-1]; } return obj_locator; }
И следующий код может определить, действителен ли указатель или нет, вам, вероятно, нужно добавить некоторую проверку NULL:
CTest* t = new CTest(); //t = (CTest*) 0; //t = (CTest*) 0x12345678; const rtti_object_locator* ptr = RTTI_GetObjectLocator(t); #ifdef _WIN64 char *base = ptr->signature == 0 ? (char*)RtlPcToFileHeader((void*)ptr, (void**)&base) : (char*)ptr - ptr->object_locator; const type_info *td = (const type_info*)(base + ptr->type_descriptor); #else const type_info *td = ptr->type_descriptor; #endif const char* n =td->name();
Это получает имя класса из указателя - я думаю, этого должно быть достаточно для ваших нужд.
Одна вещь, которую я все еще боюсь, - это производительность проверки указателя - в фрагменте кода выше уже сделано 3-4 вызова API - может быть излишним для критичных по времени приложений.
Было бы хорошо, если бы кто-нибудь мог измерить накладные расходы на проверку указателя по сравнению, например, с вызовами C # / управляемым C ++.
источник
В самом деле, что-то может быть сделано в конкретном случае: например, если вы хотите проверить, действительна ли строка указателя строки, использование системного вызова write (fd, buf, szie) может помочь вам сделать волшебство: пусть fd будет файловым дескриптором временного файл, который вы создаете для тестирования, и buf, указывающий на строку, которую вы тестируете, если указатель недействителен, write () вернет -1, а errno будет установлено значение EFAULT, что указывает на то, что buf находится за пределами вашего доступного адресного пространства.
источник
Следующее работает в Windows (кто-то предлагал это раньше):
static void copy(void * target, const void* source, int size) { __try { CopyMemory(target, source, size); } __except(EXCEPTION_EXECUTE_HANDLER) { doSomething(--whatever--); } }
Функция должна быть статическим, автономным или статическим методом какого-либо класса. Чтобы протестировать только для чтения, скопируйте данные в локальный буфер. Чтобы протестировать на запись без изменения содержимого, перепишите их. Вы можете проверить только первый / последний адреса. Если указатель недействителен, управление будет передано в doSomething, а затем за скобки. Только не используйте ничего, требующее деструкторов, например CString.
источник
В Windows я использую этот код:
void * G_pPointer = NULL; const char * G_szPointerName = NULL; void CheckPointerIternal() { char cTest = *((char *)G_pPointer); } bool CheckPointerIternalExt() { bool bRet = false; __try { CheckPointerIternal(); bRet = true; } __except (EXCEPTION_EXECUTE_HANDLER) { } return bRet; } void CheckPointer(void * A_pPointer, const char * A_szPointerName) { G_pPointer = A_pPointer; G_szPointerName = A_szPointerName; if (!CheckPointerIternalExt()) throw std::runtime_error("Invalid pointer " + std::string(G_szPointerName) + "!"); }
Применение:
unsigned long * pTest = (unsigned long *) 0x12345; CheckPointer(pTest, "pTest"); //throws exception
источник
IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr () для Windows.
Это занимает время, пропорциональное длине блока, поэтому для проверки работоспособности я просто проверяю начальный адрес.
источник
Я видел, как различные библиотеки используют какой-то метод для проверки памяти, на которую нет ссылок, и тому подобное. Я считаю, что они просто «переопределяют» методы выделения и освобождения памяти (malloc / free), которые имеют некоторую логику, отслеживающую указатели. Я полагаю, это излишек для вашего варианта использования, но это был бы один из способов сделать это.
источник
Технически вы можете переопределить оператор new (и delete ) и собрать информацию обо всей выделенной памяти, чтобы у вас был метод проверки правильности памяти кучи. но:
вам все еще нужен способ проверить, выделен ли указатель в stack ()
вам нужно будет определить, что такое «действительный» указатель:
а) память по этому адресу выделяется
б) память по этому адресу является начальным адресом объекта (например, адрес не в середине огромного массива)
в) память по этому адресу начинается адресом объекта ожидаемого типа
Итог : рассматриваемый подход не является способом С ++, вам необходимо определить некоторые правила, которые гарантируют, что функция получит действительные указатели.
источник
В C ++ нет возможности сделать эту проверку. Что делать, если другой код передает неверный указатель? Вы должны рухнуть. Зачем? Посмотрите эту ссылку: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
источник
Дополнение к аккредитованному ответу (-ам):
Предположим, что ваш указатель может содержать только три значения - 0, 1 и -1, где 1 означает действительный указатель, -1 - недопустимый и 0 - другой недопустимый. Какова вероятность того, что ваш указатель равен NULL, если все значения равновероятны? 1/3. Теперь удалите действительный случай, чтобы для каждого недопустимого случая у вас было соотношение 50:50, чтобы уловить все ошибки. Выглядит хорошо, правда? Масштабируйте это для 4-байтового указателя. Возможны 2 ^ 32 или 4294967294 значения. Из них только ОДНО правильное значение, одно - ПУСТО (NULL), и у вас все еще остается 4294967292 других недопустимых случая. Пересчитайте: у вас есть тест на 1 из (4294967292+ 1) недействительных случаев. Вероятность 2.xe-10 или 0 для большинства практических целей. Такова тщетность проверки NULL.
источник
Вы знаете, что новый драйвер (по крайней мере, в Linux), который способен на это, вероятно, будет не так уж сложно написать.
С другой стороны, было бы глупо создавать свои программы подобным образом. Если у вас нет действительно конкретного и одноразового использования для такой вещи, я бы не рекомендовал ее. Если вы создали большое приложение, загруженное с проверками допустимости постоянных указателей, оно, вероятно, будет ужасно медленным.
источник
Если они не работают - следующее обновление Windows это исправит? Если они не работают на концептуальном уровне - функция, вероятно, будет полностью удалена из windows api.
Документация MSDN утверждает, что они запрещены, и причиной этого, вероятно, является недостаток дальнейшего проектирования приложения (например, обычно вы не должны молча есть недействительные указатели - если вы, конечно, отвечаете за дизайн всего приложения), а также производительность / время проверки указателя.
Но не стоит утверждать, что они не работают из-за какого-то блога. В своем тестовом приложении я убедился, что они работают.
источник
эти ссылки могут быть полезны
_CrtIsValidPointer Проверяет, что указанный диапазон памяти допустим для чтения и записи (только отладочная версия). http://msdn.microsoft.com/en-us/library/0w1ekd5e.aspx
_CrtCheckMemory Подтверждает целостность блоков памяти, выделенных в отладочной куче (только для отладочной версии). http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx
источник