Подтвердить что ты не робот

Функция MAX/MIN в Objective C, которая позволяет избежать проблем с литьем

У меня был код в моем приложении, который выглядит следующим образом. Я получил некоторую обратную связь вокруг ошибки, когда, к моему ужасу, я положил на нее отладчик и обнаружил, что МАКСИМАЛЬНОЕ между -5 и 0 -5!

NSString *test = @"short";
int calFailed = MAX(test.length - 10, 0);                      // returns -5

Посмотрев на макрос MAX, я вижу, что он требует, чтобы оба параметра были одного типа. В моем случае "test.length" - это unsigned int, а 0 - подписанный int. Таким образом, простая задача (для любого параметра) устраняет проблему.

NSString *test = @"short";
int calExpected = MAX((int)test.length - 10, 0);                    // returns 0

Это кажется неприятным и неожиданным побочным эффектом этого макроса. Есть ли другой встроенный метод для iOS для выполнения MIN/MAX, где компилятор предупреждал бы о несовпадающих типах? Кажется, что это СЛЕДУЕТ была проблема времени компиляции, а не то, что требовало отладчика, чтобы понять. Я всегда могу написать свои собственные, но хотел бы увидеть, есть ли у кого-нибудь другие подобные проблемы.

4b9b3361

Ответ 1

Включение -Wsign-compare, как было предложено с помощью ответа FDinoff, является хорошей идеей, но я подумал, что, возможно, стоит объяснить причину этого более подробно, поскольку это довольно распространенная ошибка.

Проблема не связана с макросом MAX в частности, а с a) вычитанием из целого числа без знака способом, который приводит к переполнению, и b) (как следует из предупреждения) о том, как обрабатывается компилятор сравнение значений подписи и без знака в целом.

Первая проблема довольно легко объяснить: когда вы вычитаете из целого числа без знака и результат будет отрицательным, результат "переполняется" до очень большого положительного значения, поскольку целое число без знака не может представлять отрицательные значения. Поэтому [@"short" length] - 10 будет оцениваться как 4294967291.

Что может быть более удивительным, так это то, что даже без вычитания нечто вроде MAX([@"short" length], -10) не даст правильного результата (он будет оценивать до -10, хотя [@"short" length] будет 5, что, очевидно, больше). Это не имеет ничего общего с макросом, что-то вроде if ([@"short" length] > -10) { ... } приведет к одной и той же проблеме (код в if-блоке не будет выполняться).

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

Цитата из Понимать целые правила преобразования [cert.org]:

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

(акцент мой)

Рассмотрим следующий пример:

int s = -1;
unsigned int u = 1;
NSLog(@"%i", s < u);
// -> 0

Результат будет 0 (false), хотя s (-1) явно меньше, чем u (1). Это происходит потому, что оба значения преобразуются в unsigned int, поскольку int не может представлять все значения, которые могут содержаться в unsigned int.

Это становится еще более запутанным, если вы меняете тип s на long. Затем вы получите тот же (неверный) результат на 32-битной платформе (iOS), но в 64-битном Mac-приложении это будет работать нормально! (объяснение: long - это 64-битный тип, поэтому он может представлять все 32-битные значения unsigned int.)

Итак, длинный рассказ: не сравнивайте целые числа без знака и знака, особенно если подписанное значение потенциально отрицательно.

Ответ 2

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

warning: signed and unsigned type in conditional expression [-Wsign-compare]

Это позволяет поместить ролики в нужные места, если это необходимо, и вам не нужно переписывать макросы MAX или MIN