Лучший способ обрабатывать и сообщать об ошибках выделения памяти из-за целочисленного переполнения в Objective-C? - PullRequest
9 голосов
/ 12 февраля 2010

Для начала позвольте мне сказать, что я понимаю, как и почему может возникнуть проблема, которую я описываю. Я был специалистом в области компьютерных наук, и я понимаю арифметику переполнения / недополнения и подписанную / неподписанную. (Для тех, кто не знаком с этой темой, в Руководстве по безопасному кодированию Apple кратко обсуждается переполнение целых чисел .)

Мой вопрос касается сообщения об этой ошибке и ее исправления после ее обнаружения, а более конкретно - в случае с Objective-C. (Я пишу и поддерживаю CHDataStructures .) У меня есть несколько классов коллекций, которые выделяют память для хранения объектов и динамически расширяются по мере необходимости. Я еще не видел сбоев, связанных с переполнением, вероятно потому, что в моих тестовых примерах в основном используются вменяемые данные. Однако, учитывая непроверенные значения, вещи могут взорваться довольно быстро, и я хочу предотвратить это.

Я определил как минимум два распространенных случая, когда это может произойти:

  1. Вызывающая сторона передает очень большое значение без знака (или отрицательное значение со знаком) в -initWithCapacity:.
  2. Было добавлено достаточно объектов для динамического расширения емкости, и емкость стала достаточно большой, чтобы вызвать переполнение.

Самое простое - определить, не произойдет ли переполнение. (Например, прежде чем пытаться выделить length * sizeof(void*) байт, я могу проверить, равен ли length <= UINT_MAX / sizeof(void*), поскольку неудача в этом тесте будет означать, что продукт переполнится и потенциально выделит гораздо меньшую область памяти, чем необходимо. На платформах, которые его поддерживают, checkint.h API является еще одной альтернативой.) Сложнее всего определить, как с этим справиться. В первом сценарии вызывающий абонент, возможно, лучше подготовлен (или, по крайней мере, настроен), чтобы справиться с ошибкой. Второй сценарий может происходить в любом месте кода, в который объект добавляется в коллекцию, что может быть совершенно недетерминированным.

Итак, мой вопрос заключается в следующем: Как ожидается, что код Objective-C "добропорядочного гражданина" будет действовать, когда в этом типе ситуации происходит целочисленное переполнение? (В идеале, так как мой проект является структурой в Тот же дух, что и у Foundation в Какао, я бы хотел смоделировать так, как он ведет себя, для максимального «согласования импедансов». В найденной мной документации Apple об этом ничего особо не говорится.) Я полагаю, что в любом случай, сообщающий об ошибке, является данным. Поскольку API для добавления объекта (который может вызвать сценарий 2) не принимает параметр ошибки, что я могу сделать, чтобы помочь решить проблему, если что-нибудь? Что действительно считается нормальным в таких ситуациях? Я не хочу сознательно писать подверженный сбоям код, если смогу сделать лучше ...

Ответы [ 5 ]

4 голосов
/ 07 марта 2010

Войти и вызвать исключение.

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

3 голосов
/ 11 марта 2010

Есть две проблемы под рукой:

(1) Выделение не выполнено, и вам не хватает памяти.

(2) Вы обнаружили переполнение или другое ошибочное состояние, которое приведет к (1), если вы продолжите.

В случае (1) вы находитесь в шланге (если только неудачное распределение не было слишком глупым, и вы не знаете , что неудачное распределение было только этим). Если это произойдет, лучшее, что вы можете сделать, - это потерпеть крах как можно быстрее и оставить как можно больше доказательств. В частности, создание функции, которая вызывает abort() с именем, подобным IAmCrashingOnPurposeBecauseYourMemoryIsDepleted(), оставит свидетельство в журнале сбоев.

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

3 голосов
/ 12 февраля 2010

Что касается динамически растущего хранилища на основе массива, то здесь можно сделать очень много. Я являюсь разработчиком планировщика Moab для суперкомпьютеров, и мы также имеем дело с очень большими числами в системах с тысячами процессоров, тысячами рабочих мест и огромным количеством выходных данных. В какой-то момент вы не можете объявить буфер больше, не создавая совершенно новый тип данных для работы с размерами, превышающими UINT_MAX или LONG_LONG_MAX и т. Д., И в этот момент на большинстве «обычных» машин вы окажетесь в любом случае не хватает стека / кучи. Поэтому я бы сказал записать значимое сообщение об ошибке, не допустить взрыва коллекции, и если пользователю нужно добавить столько вещей в коллекцию CHDataStructures, он должен знать, что существуют проблемы, связанные с очень большими числами, и вызывающей стороне. следует проверить, было ли добавление выполнено успешно (отслеживать размер коллекции и т. д.).

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

1 голос
/ 07 марта 2010

Я бы сказал, что правильно сделать то, что делают коллекции Какао. Например, если у меня есть следующий код:

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSMutableArray * a = [[NSMutableArray alloc] init];

    for (uint32_t i = 0; i < ULONG_MAX; ++i) {
        for (uint32_t i = 0; i < 10000000; ++i) {
            [a addObject:@"foo"];
        }
        NSLog(@"%lu rounds of 10,000,000 completed", i+1);
    }

    [a release];

    [pool drain];
    return 0;
}

.. и просто дайте ему поработать, в конечном итоге он умрет с EXC_BAD_ACCESS. (Я скомпилировал и запустил его как 32-разрядное приложение, поэтому я могу быть уверен, что ему не хватит места при попадании 2 ** 32 объектов.

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

0 голосов
/ 09 марта 2010

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

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

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

Документация очень лаконична: Утверждения и ведение журнала .

...