Какие ненулевые адреса памяти можно использовать как значения ошибок в C? - PullRequest
1 голос
/ 07 октября 2010

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

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

Ответы [ 7 ]

4 голосов
/ 07 октября 2010

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

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

Теперь, один слегка злой вариант - использовать setjmp() и longjmp() для выхода из процедур GMP.Однако это оставит GMP в непредсказуемом состоянии - вы должны предположить, что после этого момента вы никогда не сможете снова вызвать процедуру GMP.Это также может привести к утечкам памяти ... но это, вероятно, наименьшая из ваших проблем на данный момент.

Другой вариант - иметь зарезервированный пул в системном malloc, то есть при запуске приложения:

emergencyMemory = malloc(bignumber);

Теперь, если malloc() терпит неудачу, вы делаете free(emergencyMemory), и, надеюсь, у вас есть достаточно места для восстановления.Имейте в виду, что это дает вам лишь ограниченный запас - вы должны надеяться, что GMP вернется к вашему коду (и этот код проверит и увидит, что аварийный пул был использован), прежде чем вы действительно исчерпаете память.

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

3 голосов
/ 07 октября 2010

Нет, переносимый диапазон недопустимых значений указателя отсутствует.

Вы можете использовать определения для конкретной платформы или адреса некоторых глобальных объектов:

const void *const error_out_of_bounds = &error_out_of_bounds;
const void *const error_no_sprockets = &error_no_sprockets;

[Редактировать: извините, пропустил, что вы надеялись вернуть эти значения в библиотеку. Как говорит bdonlan, вы не можете этого сделать. Даже если вы найдете «недопустимые» значения, библиотека не будет ожидать их. Требуется, чтобы ваша функция возвращала действительное значение или abort.]

Вы могли бы сделать что-то подобное в глобалах:

void (*error_handler)(void*);
void *error_data;

Тогда в вашем коде:

error_handler = some_handler;
error_data = &some_data;
mpz_init(something);

В вашем распределителе:

if (allocated_memory_ok) return the_memory;
error_handler(error_data);
abort();

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

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

Но это полностью общий вид, тогда как GMP с открытым исходным кодом. Вы можете узнать, что на самом деле происходит в mpz_init, по крайней мере, для определенного выпуска GMP. Может быть какой-то способ заранее убедиться, что у вашего распределителя достаточно памяти для выполнения запроса (-ов), или может быть какой-то способ выкручиваться, не нанося слишком много ущерба (как говорит bdonlon, longjmp).

1 голос
/ 07 октября 2010

Поскольку никто не дал правильного ответа, набор ненулевых адресов памяти, которые вы можете безопасно использовать в качестве значений ошибок, совпадает с набором адресов, созданных для этой цели.Просто объявите массив static const char (или глобальный const char, если вам нужно, чтобы он был виден глобально), размер которого N - это количество нужных вам кодов ошибок, и используйте указатели на элементы N этого массиваN значения ошибок.

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

0 голосов
/ 07 октября 2010

Возможно взять адреса библиотек C, которые гарантированно существуют и которые никогда не будут возвращены malloc или подобным. Для большей переносимости это должны быть указатели на объекты, а не указатели на функции, но приведение ((void*)main), вероятно, будет приемлемым для большинства архитектур. Один указатель данных, который мне приходит в голову, это environ, но это POSIX, или stdin и т. Д., Которые не обязательно являются "настоящими" переменными.

Чтобы использовать это, вы можете просто использовать следующее:

extern char** environ; /* guaranteed to exist in POSIX */
#define DEADBEAF ((void*)&environ)
0 голосов
/ 07 октября 2010

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

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

Вы также можете указать на зарезервированные страницы операционной системы, в Linux они занимают область от 0xc0000000 до 0xffffffff (в 32-битной системе). Из пользовательского пространства у вас не будет необходимых привилегий для доступа к этому региону.

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

Самое простое решение, это просто использовать значения, непосредственно отрицательные к 0, (-1, -2 и т. Д.), Или сразу положительные (1, 2, ...). Вы можете быть уверены, что эти адреса находятся на недоступных страницах.

0 голосов
/ 07 октября 2010

Если вы возвращаете только, например, 16-битные или 32-битные выровненные указатели, неровный адрес указателя (LSB равен 1) будет по меньшей мере «загадочным» и создаст возможность для использования моего фаворита на все времена-значение 0xDEADBEEF (для 32-разрядных указателей) или 0xDEADBEEFBADF00D (для 64-разрядных указателей).

0 голосов
/ 07 октября 2010

Гарантируется только в текущих операционных системах основного потока (с включенной виртуальной памятью) и архитектурах ЦП:

-1L (означает, что все биты в значении, достаточном для указателя)

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

Работает в HP-UX, Windows, Solaris, AIX, Linux, Free-Net-OpenBSD и с i386, amd64, ia64, parisc, sparc и powerpc.

Думаю, этого достаточно. Не вижу причин для более чем этих двух значений (0, -1)

...