Re[7]: жизнь не перестает удивлять
От: fk0 Россия https://fk0.name
Дата: 21.11.23 20:31
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>Здравствуйте, fk0, Вы писали:


fk0>> Нет. Есть принципиальная разница между циклом с goto и циклом с использованием операторов структурного программирования:

fk0>>при использовании последних все объекты создаваемые в цикле, в каждом цикле вначале заново инициализируются, а в конце
fk0>>цикла неявным образом уничтожаются. И вообще могут иметь разные адреса в каждом цикле. А с goto получается чёрти что.

K>цикл на нижнем уровне — это просто условный переход jmp или как там уж это называется.

K>бесконечный цикл может быть представлен как безусловный переход goto.

НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты
созданные в теле цикла. Это ПРИНЦИПИАЛЬНОЕ свойство цикла. Для C++ программистов оно означает,
что "for (auto& x : MakeTempObject())" не будет работать (вроде как будет пофикшено в C++23 для
этого частного случая), для C-программистов означает, что переменные объявленные в теле цикла
не обязаны сохранять свои значения (и адреса) в каждой новой итерации. Последнее легко заметно
в каком-нибудь интерпретаторе C, вроде Ch.

K>И выполнение функции — это тоже переход на нужный адрес в памяти.


Я знаю ассемблер. Но цикл и вызов функции -- это не только переход.
Переход лишь как средство реализации. И оно может оказаться совершенно другим.
Цикл может быть развёрнут, может быть tail call оптимизация, функция может быть
заинлайнена, да что угодно ещё...

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

K>Никто не мешает этому самому компилятору нормально обрабатывать и goto и уведомлять о существующих проблемах.
K>Собственно, в C# goto из цикла превращается в break.

Но никто не мешает порождать код с UB и молча молчать. И получать неинициализированные
переменные например. При прыжке вперёд через инициализацию переменной (C++ запрещает)
или прыжке назад в середину тела цикла в котором декларировались переменные.

K>А без дублирования return или break, как в структурном программировании предлагается сделать банальный поиск элемента в массиве?


Любой код может быть написан через рекурсию...

K>
K>int FindIndex(int[] arr, int value)
K>{
K>  for (int i = 0; i < arr.Length; ++i)
K>  {
K>    if (arr[i] == value)
K>      return i;
K>  }
K>  return -1;
K>}
K>

K>даже если первый элемент подойдёт, продолжим пробегать всю коллекцию, чтобы было структурно и молодёжно?

Нет. Условие прерывания цикла может переехать в оператор for, например.


K>
K>int FindIndex(int[] arr, int value)
K>{
K>  int result = -1;
K>  for (int i = 0; i < arr.Length; ++i)
K>  {
K>    if (result == -1 && arr[i] == value)
K>      result = i;
K>  }
K>  return result;
K>}
K>

K>А то ведь return внутри цикла может привести к тому, что разработчик что-то забудет удалить или сделать, должна быть одна точка выхода из функции.

Вообще-то да. В голом C или паскале. В языках с RAII это не так критично.

K>Я сам как-то goto не использую и редко такое нужно, но вот думаю: а зачем я для выхода из вложенных циклов горожу флаги и леплю лишние проверки, когда можно просто goto написать и перейти в нужную точку.


И сразу вопрос, что делать компилятору с переменными созданными в цикле?
Деструктор для них же надо вызывать? И становится goto очень неочевидным оператором,
с очень неявным control flow...
Re[8]: жизнь не перестает удивлять
От: CreatorCray  
Дата: 21.11.23 21:13
Оценка:
Здравствуйте, fk0, Вы писали:

fk0> НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты созданные в теле цикла.

В С всё на ручнике
Потом, тело цикла это то, что под for а не внутри.

fk0>Для C++ программистов оно означает, что "for (auto& x : MakeTempObject())" не будет работать

Это как раз работает даже в C++11, где range-for только появился.

Ибо range for это синтаксический сахар, который разворачивается в как минимум вот такое:
{
    auto&& __range = range-expression; <<< результат moves в __range который спокойно себе живёт вне for-loop, и переживает его
    for (auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin)
    {
        range-declaration = *__begin;
        loop-statement
    }
}

Потом ещё больше вынесли наружу, но это не суть.

Проблемы будут с "for (auto& x : MakeTempObject().getObjects())"
Но совсем не потому что for.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[8]: жизнь не перестает удивлять
От: karbofos42 Россия  
Дата: 21.11.23 21:21
Оценка:
Здравствуйте, fk0, Вы писали:

fk0> НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты

fk0>созданные в теле цикла. Это ПРИНЦИПИАЛЬНОЕ свойство цикла. Для C++ программистов оно означает,
fk0>что "for (auto& x : MakeTempObject())" не будет работать (вроде как будет пофикшено в C++23 для
fk0>этого частного случая), для C-программистов означает, что переменные объявленные в теле цикла
fk0>не обязаны сохранять свои значения (и адреса) в каждой новой итерации.

Ну, вот код на C++:
for (int i = 0; i < 10; ++i)
{
  A *arr = new A();
}

цикл каждую итерацию будет удалять созданный объект класса A или всё же память утечёт и нет никакой гарантии про все объекты?

fk0> Я знаю ассемблер. Но цикл и вызов функции -- это не только переход.

fk0>Переход лишь как средство реализации. И оно может оказаться совершенно другим.
fk0>Цикл может быть развёрнут, может быть tail call оптимизация, функция может быть
fk0>заинлайнена, да что угодно ещё...

Но технически все эти функции и циклы преобразуются в банальные goto или что-то подобное, а вся эта инициализация переменных, очистка стека — это добавляет компилятор.

fk0> Но никто не мешает порождать код с UB и молча молчать. И получать неинициализированные

fk0>переменные например. При прыжке вперёд через инициализацию переменной (C++ запрещает)
fk0>или прыжке назад в середину тела цикла в котором декларировались переменные.

Вопрос к создателям языка и компиляторов к нему. Никто им не мешает обойтись без UB

fk0> Нет. Условие прерывания цикла может переехать в оператор for, например.


В простейшем случае в итоге получим некрасивую запись и лишнюю проверку в конце, где будем выяснять причины попадания в строку
(коллекция кончилась и ничего нужного не нашлось или же наткнулись на искомый элемент и нужно его вернуть).
На деле же условие выхода из цикла будет с предварительными расчётами и в for не влезет.
Можно конечно всякого нагородить, но не понятно зачем.
Там же и читабельность кода снизится и лишние операции добавятся.

fk0> И сразу вопрос, что делать компилятору с переменными созданными в цикле?

fk0>Деструктор для них же надо вызывать?

примерно то же самое, что компилятор делает при break

fk0>И становится goto очень неочевидным оператором,

fk0>с очень неявным control flow...

у исключений с try/catch не проще и потенциальных проблем от них не меньше, но народ как-то живёт.
Ну, сложно с goto — пусть дадут оператор для выхода из всех циклов.
Бывают ситуации типа:
int value = 0; // стандартное значение
for (int i = 0; i < 10; ++i)
{
  for (int j = i + 1; j < 11; ++j)
  {
    for (int k = j + 1; k < 12; ++k)
    {
      if (...)
        value = ...;  // меняем значение на расчётное
        break; // тут хочется выйти из всех 3-х циклов
    }
  }
}
// Дальше используем value вне зависимости от того какое значение (стандартное или расчётное)

break из одного цикла почему-то не является проблемой для создателей языков.
Как и return из любой строки.
Не понимаю что не так с break из пары вложенных циклов и чем это принципиально отличается.
Можно конечно всё это в единственный while переписать или ещё чего намудрить, но читаемость кода снизится,
а по факту будет выполняться примерно то же самое, что можно с goto написать и не терять в читаемости.
И в итоге на практике не понятно что выйдет надёжнее в поддержке:
странно написанный и непонятный код по всем правилам структурного программирования или лаконичный код с сомнительным goto.
Re[9]: жизнь не перестает удивлять
От: fk0 Россия https://fk0.name
Дата: 21.11.23 22:51
Оценка:
Здравствуйте, CreatorCray, Вы писали:

CC>Здравствуйте, fk0, Вы писали:


fk0>> НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты созданные в теле цикла.

CC>В С всё на ручнике
CC>Потом, тело цикла это то, что под for а не внутри.

Всё да не всё. Формально "рантайм" освобождает память из под переменных скоупе тела цикла
и формально может назначить на следующей итерации другие адреса. Повторюсь, в каком-нибудь Ch
можно запросто напороться на то, что не работает, но строго соответствует букве стандарта.

Или второй операнд оператора for (где может быть декларация переменной) будет вычисляться каждый
раз в начале цикла и уничтожаться в конце цикла. Это же не просто так.

fk0>>Для C++ программистов оно означает, что "for (auto& x : MakeTempObject())" не будет работать

CC>Это как раз работает даже в C++11, где range-for только появился.

Да, я не прав. Не рабоатет MakeTempObject().getter(), но это другое.
Re[9]: жизнь не перестает удивлять
От: fk0 Россия https://fk0.name
Дата: 21.11.23 23:01
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>Здравствуйте, fk0, Вы писали:


fk0>> НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты

fk0>>созданные в теле цикла. Это ПРИНЦИПИАЛЬНОЕ свойство цикла. Для C++ программистов оно означает,
fk0>>что "for (auto& x : MakeTempObject())" не будет работать (вроде как будет пофикшено в C++23 для
fk0>>этого частного случая), для C-программистов означает, что переменные объявленные в теле цикла
fk0>>не обязаны сохранять свои значения (и адреса) в каждой новой итерации.

K>Ну, вот код на C++:

K>
K>for (int i = 0; i < 10; ++i)
K>{
K>  A *arr = new A();
K>}
K>

K>цикл каждую итерацию будет удалять созданный объект класса A или всё же память утечёт и нет никакой гарантии про все объекты?

Причём здесь new? Если A() будет без new (на стеке) и написать деструктор, то будет видно, что он вызывается...
А по-указателю C++-компилятор сам ничего удалять не будет. Вот переменную указателя он "удалит". Но у ней деструктор
пустой.

K>у исключений с try/catch не проще и потенциальных проблем от них не меньше, но народ как-то живёт.

K>Ну, сложно с goto — пусть дадут оператор для выхода из всех циклов.
K>Бывают ситуации типа:

K>
K>int value = 0; // стандартное значение
K>for (int i = 0; i < 10; ++i)
K>{
K>  for (int j = i + 1; j < 11; ++j)
K>  {
K>    for (int k = j + 1; k < 12; ++k)
K>    {
K>      if (...)
K>        value = ...;  // меняем значение на расчётное
K>        break; // тут хочется выйти из всех 3-х циклов
K>    }
K>  }
K>}
K>// Дальше используем value вне зависимости от того какое значение (стандартное или расчётное)
K>


Оператора выхода из всех циклов потому и нет, что он порождает ошибки и непонятно
как его сделать. Вместо него может выступать goto, может выступать return, могут выступать
более сложные условия. В некоторых языках... (bash) такой оператор есть, но лучше б не было...
В некоторых других (tcl) нет и невозможен goto, и break делается... через исключение!

K>Не понимаю что не так с break из пары вложенных циклов и чем это принципиально отличается.


Из скольки именно циклов? А если циферку числа циклов впишут, а потом код поменяют?

K>Можно конечно всё это в единственный while переписать или ещё чего намудрить, но читаемость кода снизится,

K>а по факту будет выполняться примерно то же самое, что можно с goto написать и не терять в читаемости.

Вот полезные применения goto как раз:

1) обработка ошибок, вместо отсутствующего оператора defer: https://gustedt.gitlabpages.inria.fr/defer/
2) прерывание нескольких вложенных циклов;
3) возможно ещё какие-то экзотические случаи, которые сходу не припомнить.

Но ведь заставь дурака богу молиться -- лоб расшибёт.

K>И в итоге на практике не понятно что выйдет надёжнее в поддержке:

K>странно написанный и непонятный код по всем правилам структурного программирования или лаконичный код с сомнительным goto.

См выше.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.