Информация об изменениях

Сообщение Re[8]: Carbon от 19.04.2024 11:15

Изменено 19.04.2024 11:20 vdimas

Re[8]: Carbon
Здравствуйте, Sinclair, Вы писали:

V>>В данном случае ты даёшь оценку своим фантазиям, бо архитектуры/компиляторы явно определяют своё поведение при переполнении.

S>Ошибаетесь. Я же привёл пример. Каким образом "явно определённое поведение" приводит к разнице между -O0 и -O2 для приведённого фрагмента?

1. Твой пример содержит ошибки, например, для short, т.к. значением short(value)+1 будет значение типа int, т.е. еще до рассуждений об UB необходимо исправить ошибку:
template<typename signed_integral>
bool is_max(signed_integral value) {
    return signed_integral(value + 1) < value;
}



S>Что мы и наблюдаем в приведённом примере.


2. Мы наблюдаем невладение предметом, бо в C/C++ еще есть битовые поля, для которых это однозначное UB со всеми спецификациями платформы:
int main()
{
    struct S {
        int field1 : 8,
            field2 : 7,
            flag : 1;
    } s = { 127, 63, 1 };

    std::cout
        << (is_max(s.field1) ? "wow " : "oops ")
        << (is_max(s.field2) ? "wow " : "oops ")
        << (is_max(s.flag) ? "wow " : "oops ")
        << std::endl;

    using sbyte = signed char;
    
    std::cout
        << (is_max(static_cast<sbyte>(127)) ? "wow " : "oops ")
        << (is_max(static_cast<short>(32767)) ? "wow " : "oops ")
        << std::endl;

    return 0;
}



V>>Т.е. можно подобрать такое кодирование, что в твоём коде будет UB именно для этой платформы, например, для обратного кодирования есть два нуля для знаковых чисел — 0000 и 1111, где при сравнении первый ноль меньше второго, и твоя программа закономерно поломалась, как и предостерегал стандарт.

S> Нет. Не угадали.

фуф, тяжело с тобой...
Такое ощущение, что тебе всю жизнь пришлось работать среди откровенно тупых людей. ))
Мои соболезнования, кстате.
Но что в любом обсуждении ты теперь на рефлексах считаешь собеседников по-умолчанию идиотами, и даже не мелькает мысли проверить себя — это изрядно утомляет, конечно...


S>Программа прекрасно ломается на совершенно любой платформе.


Программа ломается, потому что в ней ошибки. ))
Ты написал некий обощённый код из некоторого обощённого предположения, но это предположение для многих ситуаций ложно.

3. В общем, если уж охота поупражняться с битами без углубления в подробности, то это всегда делается на беззнаковых.
А если оперируешь знаковыми — то изволь максимально погрузиться в подробности и отвечай за каждый бит, как грится.

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

Фишка в том, что для промежуточных вычислений в железе часто используют регистры, шириной в слово, т.е. UB возникает прямо в железе — ты можешь проверить это на последних gcc на x64, там будет тот же эффект в рантайм для int, что и в случае short(value)+1, т.е. даже в рантайме программа сломается — потому что в ней ошибка — ты не можешь гарантировать ширину бит вычислений.

А ведь достаточно наложить маску 0xFFFF(FF) требуемой ширины — и код не сломается на любых флагах.
(хочешь
#include <iostream>
#include <limits.h>

template<typename signed_integral>
struct mask;

template<>
struct mask<signed char> {
    static const signed char value = 0xFF;
};

template<>
struct mask<short> {
    static const short value = 0xFFFF;
};

template<>
struct mask<int> {
    static const int value = 0xFFFFFFFF;
};

template<>
struct mask<long long> {
    static const long long value = 0xFFFFFFFFFFFFFFFFL;
};

template<typename signed_integral> 
bool is_max(signed_integral value) {
    return (static_cast<signed_integral>(value+1) && mask<signed_integral>::value) < value;
}

int main()
{
    std::cout << (is_max(static_cast<signed char>(127))? "wow" : "oops" ) << std::endl;
    std::cout << (is_max(SHRT_MAX)? "wow" : "oops" ) << std::endl;
    std::cout << (is_max(INT_MAX)? "wow" : "oops" ) << std::endl;
    std::cout << (is_max(static_cast<long long>(0x7FFFFFFFFFFFFFFFL))? "wow" : "oops" ) << std::endl;
    return 0;
}


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

(а ваше это обсуждение с коллегой было забавным конечно, я потом ниже почитал — вы в упор не увидели сразу 2 ошибки)
Re[8]: Carbon
Здравствуйте, Sinclair, Вы писали:

V>>В данном случае ты даёшь оценку своим фантазиям, бо архитектуры/компиляторы явно определяют своё поведение при переполнении.

S>Ошибаетесь. Я же привёл пример. Каким образом "явно определённое поведение" приводит к разнице между -O0 и -O2 для приведённого фрагмента?

1. Твой пример содержит ошибки, например, для short, т.к. значением short(value)+1 будет значение типа int, т.е. еще до рассуждений об UB необходимо исправить ошибку:
template<typename signed_integral>
bool is_max(signed_integral value) {
    return signed_integral(value + 1) < value;
}



S>Что мы и наблюдаем в приведённом примере.


2. Мы наблюдаем невладение предметом, бо в C/C++ еще есть битовые поля, для которых это однозначное UB со всеми спецификациями платформы:
int main()
{
    struct S {
        int field1 : 8,
            field2 : 7,
            flag : 1;
    } s = { 127, 63, 1 };

    std::cout
        << (is_max(s.field1) ? "wow " : "oops ")
        << (is_max(s.field2) ? "wow " : "oops ")
        << (is_max(s.flag) ? "wow " : "oops ")
        << std::endl;

    using sbyte = signed char;
    
    std::cout
        << (is_max(static_cast<sbyte>(127)) ? "wow " : "oops ")
        << (is_max(static_cast<short>(32767)) ? "wow " : "oops ")
        << std::endl;

    return 0;
}



V>>Т.е. можно подобрать такое кодирование, что в твоём коде будет UB именно для этой платформы, например, для обратного кодирования есть два нуля для знаковых чисел — 0000 и 1111, где при сравнении первый ноль меньше второго, и твоя программа закономерно поломалась, как и предостерегал стандарт.

S> Нет. Не угадали.

фуф, тяжело с тобой...
Такое ощущение, что тебе всю жизнь пришлось работать среди откровенно тупых людей. ))
Мои соболезнования, кстате.
Но что в любом обсуждении ты теперь на рефлексах считаешь собеседников по-умолчанию идиотами, и даже не мелькает мысли проверить себя — это изрядно утомляет, конечно...


S>Программа прекрасно ломается на совершенно любой платформе.


Программа ломается, потому что в ней ошибки. ))
Ты написал некий обощённый код из некоторого обощённого предположения, но это предположение для многих ситуаций ложно.

3. В общем, если уж охота поупражняться с битами без углубления в подробности, то это всегда делается на беззнаковых.
А если оперируешь знаковыми — то изволь максимально погрузиться в подробности и отвечай за каждый бит, как грится.

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

Фишка в том, что для промежуточных вычислений в железе часто используют регистры шириной в слово, т.е. UB возникает прямо в железе — ты можешь проверить это на последних gcc на x64, там будет тот же эффект в рантайм для int, что и в случае short(value)+1, т.е. даже в рантайме программа сломается, потому что в ней ошибка — ты не можешь гарантировать ширину бит промежуточных вычислений.

А ведь достаточно наложить маску 0xFFFF(FF) требуемой ширины — и код не сломается на любых флагах.
(хочешь
#include <iostream>
#include <limits.h>

template<typename signed_integral>
struct mask;

template<>
struct mask<signed char> {
    static const signed char value = 0xFF;
};

template<>
struct mask<short> {
    static const short value = 0xFFFF;
};

template<>
struct mask<int> {
    static const int value = 0xFFFFFFFF;
};

template<>
struct mask<long long> {
    static const long long value = 0xFFFFFFFFFFFFFFFFL;
};

template<typename signed_integral> 
bool is_max(signed_integral value) {
    return (static_cast<signed_integral>(value+1) && mask<signed_integral>::value) < value;
}

int main()
{
    std::cout << (is_max(static_cast<signed char>(127))? "wow" : "oops" ) << std::endl;
    std::cout << (is_max(SHRT_MAX)? "wow" : "oops" ) << std::endl;
    std::cout << (is_max(INT_MAX)? "wow" : "oops" ) << std::endl;
    std::cout << (is_max(static_cast<long long>(0x7FFFFFFFFFFFFFFFL))? "wow" : "oops" ) << std::endl;
    return 0;
}


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

(а ваше это обсуждение с коллегой было забавным конечно, я потом ниже почитал — вы в упор не увидели сразу 2 ошибки)