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

StackOverflow https://stackoverflow.com/questions/2249892

Вопрос

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

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

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top