Блог пользователя Konstantin.Zakharov

Автор Konstantin.Zakharov, 10 лет назад, По-русски

Здравствуйте. Сегодня я пробовал сдавать задачи без своей visual studio 2012, используя ideone.com. У меня не заработало Binary Search Tree, написанное на векторе. Точнее, оно выдавало неправильные ответы. После некоторого упрощения, тот же код стал давать ошибку исполнения.

После нескольких тщетных попыток понять в чем дело я скопировал код в студию, поставил несколько точек останова, начал отладку. Все верно. Убрал точки, запустил — а оно работает.

Поменял конфигурацию Debug на Release, запустил — ошибка исполнения.

Попробовал скомпилировать на MinGW 4.8.1 (tdm-2) — ошибка исполнения, независимо от наличия флага -O.

Cделал пример как можно меньше, но даже после этого результат прежний — signal 11 (SIGSEGV), обращение к не принадлежащей области памяти.

#include <vector>

namespace Ex {

  std::vector<int> next; // next[i] = index of next element. -1 if it's not exist

  int rec(int ind = 0) {
    if( ind == -1 ) {
      next.push_back(-1);
      return next.size() - 1;
    }
    next[ind] = rec(next[ind]);    
    return ind;
  }

}

int main() {

  Ex::next.push_back(-1);

  for(int i = 0; i < 20; i++)
    Ex::rec();

  // expected Ex::next states
  // -1
  // 1 -1
  // 1 2 -1
  // 1 2 3 -1
  // ...
  // 1 2 3 ... 20 -1

  return 0;
}

Я уже понял, что на ideone мне лучше не писать, но все-таки, может кто-то объяснит в чем дело, или все же на этот раз виновен компилятор?) Самому не очень верится, т.к. впервые вижу чтобы не работало на обоих.

  • Проголосовать: нравится
  • +3
  • Проголосовать: не нравится

»
10 лет назад, # |
Rev. 2   Проголосовать: нравится +1 Проголосовать: не нравится

Проблема в строке:

next[ind] = rec(next[ind]);  

Чтобы проблемы не было, код можно написать так:

int t = rec(next[ind]); 
next[ind] = t; 

Как по мне, это баг компилятора. Однако есть тень сомнения, а нету ли здесь случайно "неоднократного изменения переменной в пределах одной точки следования", что вызывает UB? (Это может быть связано с тем, что адрес переменной next[ind] может измениться после вызова функции и реалокации содержимого вектора). А может быть и в рекурсии дело...

  • »
    »
    10 лет назад, # ^ |
      Проголосовать: нравится +9 Проголосовать: не нравится

    По стандарту всё ок, компилятор вправе вычислить сначала ссылку next[ind], а затем вычислить rec(...) rec может сделать push_back который может перевыделить память и тем самым ссылка становится невалидной. Сам наступал на эти грабли и где-то здесь уже писал.

»
10 лет назад, # |
  Проголосовать: нравится +16 Проголосовать: не нравится

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

И как уже написал MrDindows дело в том что, адрес куда записывать next[ind] был один а после выполнения rec(...) стал другим, так как там вектор увеличил размер и перевыделил память, и соответственно адреса элементов поменялись.

Полагаю, что это ожидаемый результат, так как приоритет [] выше чем у оператора = http://ru.cppreference.com/w/cpp/language/operator_precedence

Получается сначала вычислется ссылка next[ind], а потом то что справва от =. Ну и на какой-то итерации это рантаймит :)

  • »
    »
    10 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится +5 Проголосовать: не нравится

    Утром в голову пришел гораздо менее синтетический пример — DSU с авторасширением.

    Теперь у меня стабильный RE на GNU C++ / MINGW и такой же стабильный AC на msvc2010/2012.

    В прошлой правке код, тест и скрины вывода.

    Меня интересует, верно ли утверждение — "присвоение a[i] = f(), где "f" вызывает реаллокацию "a" дает Undefined behavior (ok/RE) на msvc, тогда как на g++ это RE". По-моему это так, потому что DSU на вижаке правильно работает, а в примере из поста фейлит, как и g++.

    • »
      »
      »
      10 лет назад, # ^ |
      Rev. 2   Проголосовать: нравится +14 Проголосовать: не нравится

      присвоение a[i] = f(), где "f" вызывает реаллокацию "a" дает Undefined behavior

      Да.

      Порядок вычисления операндов любого C++ оператора, включая порядок вычисления аргументов функции в выражении вызова функции, и порядок вычисления вложенных выражений внутри любого выражения, не определён (за исключением случаев описанных ниже)

      ...

      a[i] = i++; // undefined bevahior (http://ru.cppreference.com/w/cpp/language/eval_order)

      • »
        »
        »
        »
        10 лет назад, # ^ |
          Проголосовать: нравится +3 Проголосовать: не нравится

        Спасибо, понял свой затуп.

        Самое комичное, что у меня в данный момент два вроде как разных по содержанию поста в блоге (с одинаковым названием и разницей в два года), и в обоих мне подсказали одно и то же UB. Похоже, опыт в этих делах плохо помогает, если специально не писать каких-то экстремальных кодов (MrDindows тоже на компилятор подумал). Надо специально изучать примеры красиво выглядящего UB разных видов, чтобы не допускать.

»
10 лет назад, # |
  Проголосовать: нравится -27 Проголосовать: не нравится

Typical C++