Возможная проблема с `PriorityQueue & lt; Integer & gt;` при использовании для значений, приближающихся к Integer.Max_Value` [duplicate]

Хотя синтаксис выражений типа a = a++ или a++ + a++ является законным, поведение этих конструкций не определено, потому что должно в стандарте C не соблюдаться. C99 6.5p2 :

  1. Между предыдущей и следующей точкой последовательности объект должен иметь неизменяемое значение хранимого значения не более одного раза путем оценки выражения. [72] Кроме того, предыдущее значение должно быть считано только для определения сохраняемого значения [73]
blockquote>

С помощью сноски 73 далее уточняется, что

  1. Этот абзац отображает неопределенные выражения операторов, такие как
    i = ++i + 1;
    a[i++] = i;
    
    , позволяя
    i = i + 1;
    a[i] = i;
    
blockquote>

. В списке перечислены различные точки последовательности в приложении C к C11 C99 ):

  1. Ниже приведены точки последовательности, описанные в 5.1.2.3: Между оценки указателя функции и фактических аргументов в вызове функции и фактическом вызове. (6.5.2.2). Между оценками первого и второго операндов следующих операторов: логическое И & amp; & amp; (6.5.13); логический ИЛИ || (6.5.14); запятая, (6.5.17). Между оценками первого операнда условного? : оператор и в зависимости от второго и третьего операндов (6.5.15). Конец полного декларатора: деклараторы (6.7.6); Между оценкой полного выражения и следующим полным выражением, которое должно быть оценено. Ниже приведены полные выражения: инициализатор, не являющийся частью составного литерала (6.7.9); выражение в выражении выражения (6.8.3); управляющее выражение оператора выбора (if или switch) (6.8.4); управляющее выражение while или do (6.8.5); каждое из (необязательных) выражений оператора for (6.8.5.3); (необязательное) выражение в операторе return (6.8.6.4). Непосредственно перед возвратом функции библиотеки (7.1.4). После действий, связанных с каждым форматированным спецификатором преобразования функции ввода / вывода (7.21.6, 7.29.2). Непосредственно перед и сразу после каждого вызова функции сравнения, а также между любым вызовом функции сравнения и любым перемещением объектов, переданных в качестве аргументов для этого вызова (7.22.5).
blockquote>

Формулировка того же абзаца в C11 :

  1. Если побочный эффект на скалярный объект не влияет на какой-либо другой побочный эффект на один и тот же скалярный объект или вычисление значения с использованием значения одного и того же скалярного объекта, поведение не определено. Если существует несколько допустимых порядков подвыражений выражения, поведение не определено, если такой какой-либо побочный эффект возникает в любом из порядков.84)
blockquote>

Вы можете обнаружить такие ошибки в программе, например, используя последнюю версию GCC с -Wall и -Werror, а затем GCC полностью откажется от компиляции вашей программы. Ниже приведен вывод gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function ‘main’:
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

Важная часть состоит в том, чтобы знать , что точка последовательности - и что такое точка последовательности, а что нет . Например, оператор запятой является точкой последовательности, поэтому

j = (i ++, ++ i);

четко определен и будет увеличивать i на единицу, выдает старое значение, отбрасывает это значение; затем в операторе запятой уложите побочные эффекты; а затем приращение i на единицу, и результирующее значение становится значением выражения - т. е. это всего лишь ухищренный способ написать j = (i += 2), который снова является «умным» способом записи

i += 2;
j = i;

Тем не менее, , в списках аргументов функции не - оператор запятой, и нет точки последовательности между оценками различных аргументов; вместо этого они не зависят от друг друга; поэтому вызов функции

int i = 0;
printf("%d %d\n", i++, ++i, i);

имеет неопределенное поведение , потому что нет никакой точки последовательности между оценками i++ и ++i в аргументах функции, а значение i поэтому изменяется дважды, как i++, так и ++i, между предыдущей и следующей точками последовательности.

76
задан jahroy 17 May 2013 в 05:04
поделиться

5 ответов

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

85
ответ дан Itay Maman 27 August 2018 в 02:57
поделиться

Возможно, чтобы избежать переполнения / недостаточного потока.

3
ответ дан Adamski 27 August 2018 в 02:57
поделиться

Проще говоря, тип int недостаточно велик, чтобы сохранить разницу между двумя произвольными значениями int. Например, разница между 1,5 миллиардами и -1,5 миллиардами составляет 3,0 миллиарда, но int не может содержать значения, превышающие 2,1 миллиарда.

9
ответ дан fredoverflow 27 August 2018 в 02:57
поделиться

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

  • Первая версия compareTo возвращает один из трех возможные значения: -1, 0 или 1.
  • Если вы замените последнюю строку на вычитание, результатом может быть любое целочисленное значение.

Если вы знаете, не будет переполнения, вы можете использовать что-то вроде этого:

public int compareTo(Integer anotherInteger) {
    return sign(this.value - anotherInteger.valuel);
}
1
ответ дан PauliL 27 August 2018 в 02:57
поделиться

Вычитание «трюк» для сравнения двух числовых значений ломается !!!

        int a = -2000000000;
        int b =  2000000000;
        System.out.println(a - b);
        // prints "294967296"

Здесь a < b, но a - b положителен.

DO NOT используйте эту идиому. Это не работает.

Кроме того, , даже если он работает , он NOT обеспечит существенное улучшение производительности и может фактически стоить читабельность.

См. также

  • Java Puzzlers Головоломка 65: Странная сага о подозрительной сортировке Эта головоломка имеет несколько уроков. Наиболее конкретным является: Не используйте компаратор на основе вычитания, если вы не уверены, что разница между значениями никогда не будет больше Integer.MAX_VALUE. В более общем плане, остерегайтесь переполнения int. Еще один урок состоит в том, что вам следует избегать «умного» кода. Стремитесь писать четкий, правильный код и не оптимизировать его, если это не доказывает необходимость.
60
ответ дан polygenelubricants 27 August 2018 в 02:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: