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

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

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

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

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

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

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


person Quinn Taylor    schedule 12.02.2010    source источник


Ответы (5)


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

(1) Не удалось выделить, и у вас недостаточно памяти.

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

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

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

person bbum    schedule 11.03.2010
comment
К счастью, поскольку рассматриваемый код представляет собой низкоуровневую структуру коллекций, я не могу особенно беспокоиться о каких-либо пользовательских данных, поскольку я понятия не имею, как их сохранить или проверить на наличие повреждений. В тех немногих местах, где может произойти (2), я планирую сделать что-то разумное, например, попытаться уменьшить выделение (если это растущая коллекция) или вернуть nil. В любом случае, я обязательно зарегистрирую доказательства, чтобы помочь отследить проблему. Спасибо за полезный контекст для этого. - person Quinn Taylor; 14.03.2010

Зарегистрируйте и создайте исключение.

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

person Steve Weller    schedule 07.03.2010
comment
Это хороший ответ, но я могу выбрать только один, а ответ @bbum содержит дополнительные полезные детали и контекст. - person Quinn Taylor; 14.03.2010

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

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

person alesplin    schedule 12.02.2010

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

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 объекта.

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

person Dave DeLong    schedule 07.03.2010

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

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

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

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

person Nick Toumpelis    schedule 09.03.2010