Странная проблема с программами, оптимизированными для C/C++ (-O2)

Программа A с глобальными переменными x и y:

#include <cstdio>
#include <vector>
using namespace std;

const int maxn = 3000000 + 10;
int dp[maxn], n, maxy;
int x, y;        // <-- Global variables `x` and `y`
vector<int> a[maxn];

int main() {
    freopen("demo.txt", "r", stdin);
    freopen("demo.out", "w", stdout);

    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        //int x, y;
        scanf("%d %d", &x, &y);
        a[y].push_back(x-1);
        maxy = max(maxy, y);
    }
    for (int i = 1; i <= maxy; i++) {
        dp[i] = dp[i - 1];
        if (a[i].size() == 0) continue;
        for (int j = 0; j < a[i].size(); j++) {
            int u = a[i][j];
            dp[i] = max(dp[i], dp[u] + i - u);
        }
    }
    printf("%d\n", dp[maxy]);
    
    return 0;
}

Программа B, почти такая же, как программа A, но с локальными переменными x и y (x и y можно рассматривать как две конечные точки интервала, что $x < y$).

#include <cstdio>
#include <vector>
using namespace std;

const int maxn = 3000000 + 10;
int dp[maxn], n, maxy;
//int x, y;
vector<int> a[maxn];

int main() {
    freopen("demo.txt", "r", stdin);
    freopen("demo2.out", "w", stdout);

    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int x, y;         // <-- Local variables `x` and `y`
        scanf("%d %d", &x, &y);
        a[y].push_back(x-1);
        maxy = max(maxy, y);
    }
    for (int i = 1; i <= maxy; i++) {
        dp[i] = dp[i - 1];
        if (a[i].size() == 0) continue;
        for (int j = 0; j < a[i].size(); j++) {
            int u = a[i][j];
            dp[i] = max(dp[i], dp[u] + i - u);
        }
    }
    printf("%d\n", dp[maxy]);
    
    return 0;
}

Инструкция компиляции (с оптимизацией -O2):

g++ demo.cpp -o demo.exe -O2 -std=c++14

Скомпилируйте информацию:

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=C:/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/8.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Thread model: posix
gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)

Эти две программы получают разные результаты при одних и тех же входных данных!

Используя одни и те же входные данные, я просмотрел переменные x, y, maxy, a[i], и все они получили одинаковые данные. А элемент массива dp[i] получил другие данные. Я не знаю почему, переменные dp[i] и x, y не связаны!

И если я скомпилирую их без инструкции -O2, то есть g++ demo.cpp -o demo.exe -std=c++14, они получат тот же результат!

Большие входные данные можно найти здесь: https://ww0.lanzouq.com/iesib26alt6f

Это файл txt, содержащий около 150 001 строки.

КСТАТИ: Эта программа является решением этого вопроса , однако вы можете игнорировать эту информацию. Проблема, с которой я столкнулся, похоже, не имеет никакого отношения к этому вопросу.

Вы пробовали запустить его с дезинфицирующим средством, просто чтобы проверить неопределенное поведение?

Adrian Jałoszewski 02.08.2024 13:18

Вы уверены, что scanf действительно получится? Поскольку вы просто игнорируете его возвращаемое значение...

Dan Mašek 02.08.2024 13:21

@AdrianJałoszewski, какой код UB? Это int j = 0? Я перешел на vector<int>::size_type j = 0 и столкнулся с той же проблемой.

xaero 02.08.2024 13:25

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

Naveed Ahmed 02.08.2024 13:26

В качестве примечания: рассмотрите возможность использования более читаемых имен переменных, чем просто a, n, u, dp и т. д. — наличие нескольких строк типа dp[i] = max(dp[i], dp[u] + i - u); затрудняет анализ кода (по крайней мере, для меня).

CharonX 02.08.2024 13:28

Насколько велик ваш стек? Некоторые из ваших массивов довольно велики. Имеет ли значение статическое значение dp[]?

lastchance 02.08.2024 13:41

Если первый scanf("%d %d", &x, &y); не сработал, ваша программа имеет неопределенное поведение, поскольку вы используете неинициализированные переменные. Если y когда-либо меньше 0 или больше 3000009, то ваша программа имеет неопределенное поведение, поскольку вы получаете доступ к a за пределами границ. Если x когда-либо меньше 1 или больше 3000010, то ваша программа имеет неопределенное поведение, поскольку вы получаете доступ к dp за пределами границ.

Caleth 02.08.2024 13:52
d[0] никогда не инициализировался на первой итерации второго цикла, поэтому считываемое значение может быть любым.
user1139069 02.08.2024 13:58

@user1139069 глобальные переменные всегда инициализируются значением (по стандарту), они всегда будут содержать 0, это поведение определяется стандартом.

Ahmed AEK 02.08.2024 14:10

Ссылка на задание требует регистрации. Лучше скопировать описание задачи в вопрос.

Marek R 02.08.2024 14:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
100
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

ваш входной файл в строке 3076 содержит

0 437122

тогда в вашем коде вы выполняете доступ за пределы границ при выполнении

a[y].push_back(x-1); // a[y][last] = -1
...
int u = a[i][j];
dp[i] = max(dp[i], dp[u] + i - u);  // dp[u] = dp[-1]

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

вы можете обнаружить эту ошибку с помощью утверждения.

assert(u >= 0 && u < maxn);

в качестве альтернативы вы можете использовать std::array<int,maxn> dp; и надеяться, что ваша стандартная библиотека имеет утверждения для оператора скобки в режиме отладки, как это делает MSVC.

Поскольку вы начинаете цикл через dp с 1, вы вычитаете 1 из x, и любые 0, которые вы читаете, становятся -1, что является недопустимыми индексами.

Вы можете это исправить, выполнив цикл с 0. Я использовал std::vector::at при первом доступе к индексу, потому что он проверен. Переключение на std::vector также означает, что вещи подходящего размера.

Н.б. Гораздо лучше объявлять переменные в момент их инициализации или сразу же при чтении из ввода.

int main() {
    freopen("demo.txt", "r", stdin);
    freopen("demo2.out", "w", stdout);

    int n, maxy = -1;
    if (scanf("%d", &n) != 1) return 1;
    std::vector<std::vector<int>> a(n);
    for (int i = 0; i < n; i++) {
        int x, y;
        if (std::scanf("%d %d", &x, &y) != 2) return 2;
        a.at(y).push_back(x);
        maxy = std::max(maxy, y);
    }
    std::vector<int> dp(maxy + 1);
    int prev = 0;
    for (int i = 0; i <= maxy; i++) {
        int& dpi = dp[i];
        dpi = prev;
        for (int u : a[i]) {
            dpi = std::max(dpi, dp.at(u) + i - u);
        }
        prev = dpi;
    }
    printf("%d\n", dp.back());
}

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