С помощью GCC6 и приведенного ниже фрагмента кода этот тест
if (i > 31 || i < 0) {
это ложный, и этот printf выполняется
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
и производит очень странный вывод (GCC6):
i > 31 || i < 0 is FALSE, where i=32 /* weird result with GCC6 !!! */
тогда как с GCC4 я получаю:
i > 31 || i < 0 is true, where i=32 /* result Ok with GCC4 */
что выглядит прекрасно.
Как это может быть??
static int check_params(... input parameters ...) {
/* Note that value can be 0 (zero) */
uint32_t value = ....
int i;
i = __builtin_ctz(value);
if (i > 31 || i < 0) {
printf("i > 31 || i < 0 is true, where i=%d", i);
/* No 1 found */
return 0;
} else {
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
}
return i;
}
Согласно документация о встроенных функциях GCC, следует избегать вызова __builtin_ctz (0):
Built-in Function: int __builtin_ctz (unsigned int x) Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined.
Итак, очевидно, что решение ошибки кодирования состоит в том, чтобы просто проверить значение перед вызовом __builtin_ctz (value). Это ясно и понятно.
Я мог бы просто остановиться на этом и перейти к другим темам ... но все же я не понимаю, как я мог (с неработающим кодом) получить следующий результат:
i > 31 || i < 0 is FALSE, where i=32 /* weird result with GCC6 !!! */
Странная оптимизация GCC6 или что-то в этом роде?
На всякий случай это вообще имеет значение:
Cross-compiler: arm-linux-gcc
Architecture: -march=armv7-a
Любая идея?
для возможного получения i=32 тестовое значение должно быть 0, и согласно вашему собственному вопросу и документации, тестовое значение 0 приводит к неопределенному поведению. С неопределенным поведением может случиться что угодно. Не ожидайте надежных результатов при вызове неопределенного поведения. Даже не ожидайте повторяющихся результатов.





За исключением неопределенного поведения, __builtin_ctz всегда будет возвращать число от 0 до 31, и GCC это знает. Следовательно, проверка i > 31 || i < 0 всегда будет ложной (опять же, за исключением неопределенного поведения), и ее можно оптимизировать.
Если вы посмотрите на сгенерированную сборку, вы увидите, что условие вообще не отображается в коде (как и тогда).
Хорошо, поэтому мне нужно выяснить, как вывести код сборки (я прочту руководство) и сделать это с помощью GCC4, а также GCC6, чтобы сравнить их. Спасибо.
@Bludzee Вы можете вывести сборку в GCC, используя опцию -S. Вы также можете использовать обозреватель компилятора Godbolt для быстрого просмотра сборки, созданной различными версиями различных компиляторов, в режиме онлайн без их установки.
Очень полезно. Спасибо!
Неопределенное поведение не означает, что «значение будет произвольным». Это означает, что компилятор может выполнять буквально все, что он хочет. В этом случае похоже, что компилятор смог статически проверить, что пока value не равен 0, i всегда будет между 0 и 31 включительно. Таким образом, он даже не беспокоится о создании кода для предложения then.
Тебе просто повезло, что демоны не вышли из твоего носа.
См. Также: Неопределенное поведение может привести к путешествию во времени, Преждевременное уныние, Почему неопределенное поведение может вызывать никогда не вызываемую функцию и много-много-много других обсуждений UB здесь.
Компилятор предполагает, что неопределенного поведения не происходит. Он может сделать это предположение, потому что, если ограничения нарушены и поведение не определено, возможен результат любой, включая результат, который был бы результатом неправильного предположения.
Если неопределенного поведения нет, то i не может быть отрицательным или больше 31. На этом основании условие в операторе if может быть оптимизировано во время компиляции.
Фактическое значение, напечатанное printf, невозможно предсказать, поэтому он фактически вызывает printf с любым i. В данном случае получилось 32, но могло быть что угодно.
В стандарте C отмечается, что реализации обычно рассматривают неопределенное поведение как приглашение компиляторам вести себя задокументированным образом, характерным для среды, а в Rationale отмечается, что классификация поведения как UB предназначена для качественных реализаций, предоставляющих функции, выходящие за рамки те, которые предусмотрены Стандартом, когда они требуются рынком. Тем не менее, судя по их действиям, некоторые поставщики компиляторов считают, что больше пользы от того, чтобы компиляторы искали умные способы использования свободы бессмысленной обработки определенных случаев, чем если бы они принимали разумно ограниченное поведение, когда это практически возможно (например, наличие __builtin_ctz ( ) выберите в режиме Unspecified между тем, что делает CPU, или производством некоторого произвольного значения).
Лично я очень сомневаюсь, что ценность любых «оптимизаций», которые могут быть облегчены, если позволить компиляторам делать что-либо, кроме того, чтобы либо выдавать значение, либо выполнять операцию ЦП с любыми естественными последствиями, которые в результате могут быть хоть сколько-нибудь близки к значению, позволяющему программистам предполагать что на платформах, где сам ЦП не делает ничего странного, операция просто приведет к неопределенному результату без побочных эффектов. Тем не менее, авторы gcc, похоже, думают, что требование от программистов добавления дополнительного исходного кода для обработки случаев, которые можно было бы обрабатывать без дополнительного машинного кода, каким-то образом улучшит «эффективность».
Неопределенное поведение не определено.