Переключатель (ссылка на статью)


Комментарии:

2021-04-01 Александр Коновалов

В языках Си и Паскаль метками оператора case могут быть только целочисленные константы (включая константы перечислимого типа). И это неслучайно, так сделано для повышения производительности. Если диапазон между наибольшей и наименьшей меткой сравнительно небольшой и метки в этом диапазоне уложены плотно (при этом веток с одинаковыми метками быть не должно), то вместо цепочки ветвлений переключатель компилируется в вычислимый goto:
...

Если язык допускает нецелочисленные метки для переключателя, либо переменные в метках, то переключатель в таком случае будет лишь синтаксическим сахаром для условного оператора.

2021-04-03 alextretyak

Если диапазон между наибольшей и наименьшей меткой сравнительно небольшой и метки в этом диапазоне уложены плотно
Данное условие для оптимизации конструкции switch соблюдать вовсе не обязательно, т.к. можно использовать HashMap {}, у него также сложность будет O(1).

Если язык допускает нецелочисленные метки для переключателя
Эта проблема аналогично решается с помощью HashMap, т.к. ключи у него вполне могут быть нецелочисленного типа, например строками.


... (Прошло 3 с половиной года) ...


2024-12-01 alextretyak

Предлагаю обсуждение break в switch продолжить уже здесь, чтобы не разводить оффтоп на странице «Продолжение цикла и выход из него».
(Начало обсуждения здесь.)

Автор сайта
проваливание после case — 7 раз,
Похоже, вы посчитали пустые case за "проваливание". Я считаю, их не нужно учитывать, т.к. фактически никакое "проваливание" в таких случаях не требуется: синтаксис языка программирования должен позволять выражать намерение программиста более явно, т.е. вместо case 30: case 31: лучше использовать запись case 30, 31: или аналогичную.
И в данном файле реальное "проваливание" встречается только в одном месте: в last1120c/c00.c, строки 129-131.
(Мой скрипт-анализатор также выдал только один этот случай проваливания для данного исходного файла.)

заканчивается goto — 23 раза
Похоже, вы посчитали все goto внутри всех case. Полагаю, что считать нужно только goto в конце case, т.к. только там они "конкурируют" с break. goto в конце case я насчитал в этом файле 16 штук (что, впрочем, тоже очень много).

заканчивается return — 26 раз
Аналогично. Вы посчитали все return внутри всех switch'ей. return в конце case в этом файле 18 штук.

заканчивается exit — 2 раза
exit внутри switch в данном файле всего один (в 356-й строке), причём расположен он не в конце case, поэтому я бы его вообще не стал учитывать.
А за второй exit внутри switch вы, похоже, приняли вот этот — last1120c/c00.c, строка 444.
Он располагается внутри if сразу после switch, но не внутри него.

Не ахти какая статистика, но проваливание используется нечасто.
Однако. Ваша статистика заставила меня крепко задуматься. Вы посчитали случаев "проваливания" в 7 раз больше, чем я. И всё равно по вашим цифрам получается, что «проваливание используется нечасто».
Я почему-то решил считать не отдельные case'ы, в которых используется "проваливание", а switch'и, в которых есть хотя бы один case с "проваливанием". А ведь это очень большая разница! Допустим, у нас есть 10 switch'ей. И в каждом по 10 case'ов. Если "проваливание" используется только в одном-единственном case, то статистика по switch'ам покажет использование "проваливания" в 10% случаев. А статистика по case'ам — лишь в 1% случаев!
Если пересчитать статистику по-новому, то в результате получаются такие цифры:
Общее количество case'ов, не считая пустые: 326.
Case'ов, в которых используется "проваливание": 13. Т.е. всего 4% (против 18%, если считать по-старому)!
Если же говорить про исходный код ядра Linux 1.0, то "проваливание" используется только в 22 case'ах из 1143, т.е. лишь в 2% (против 8%) случаев!

В отличие от then в Паскале после if условие. Там оно так себе
А что не так с then в Паскале? На русский это слово переводится как «тогда» или «то» (и, например, в Глаголе используется именно такой перевод), и получается что if a < 0 then return на русском можно записать как если а < 0 то возврат.

Думаю, для проваливания вниз было бы уместно использовать ключевое слово then вместо fallthrough.
Не уверен, что это хорошая идея. Хотя fallthrough мне тоже не нравится, несмотря на то, что именно такое ключевое слово используется во многих языках. Но давайте оставим выбор английского варианта названия тем, кто очень хорошо знает английский. Мне же гораздо интереснее выбрать русский вариант для этого ключевого слова. Может, у вас есть какие-то идеи? Я что-то так сходу удачного варианта подобрать не могу.

Клихальт
Под уменьшением количества кода Вы верно имели ввиду уменьшение количества исходного текста программы, а не количества кода программы?
Да. Ведь когда говорят, что «размер/объём программы: 100 тыс. строк кода», под «кодом» понимают исходный код (source code) программы [см. https://ru.wikipedia.org/wiki/Количество_строк_кода]. Объём сгенерированного компилятором машинного кода сейчас вообще мало кого интересует.

позволю отослать Вас к приведённому мною примеру исходных текстов компилятора языка Си, где break вообще практически не используется для выхода из ветки оператора выбора.
Хорошо. Только давайте будем более объективными. Да, конкретно в исходном файле last1120c/c00.c нет ни одного break в switch. Но вот в файле prestruct/c02.c break'ов в switch — 9, из них 6 располагается в конце case (причём не последнего в switch).
Во всём исходном тексте этого компилятора таких break в switch — 10 штук. Т.е. тех, которые в конце не последнего case и которые можно убрать из кода/текста программы при использовании поведения «break по умолчанию».
Псевдооператор "проваливания" потребуется (как я уже писал чуть выше) в 13 случаях.
Результат (10 − 13) получается, конечно, не в пользу «break по умолчанию», но если посмотреть на статистику для исходного кода ядра Linux 1.0, то картина получится совсем иная.
Псевдооператор "проваливания" потребуется (как я уже писал чуть выше) в 22 случаях.
А вот таких break в switch, которые можно убрать из кода/текста программы при использовании поведения «break по умолчанию»: 498.
Итого, количество строк кода (точнее, операторов в коде) сократится при таком поведении на 476 (498 − 22).

2024-12-01 Gudleifr

Если уж перезапускаете тему, то начинайте, пожалуйста, сначала. Зачем нужен переключатель?

2024-12-01 Вежливый Лис

интереснее выбрать русский вариант для этого ключевого слова. Может, у вас есть какие-то идеи?
— Мама, я не могу остановиться!!!
— Вперёд! Вперёд! Вперёд!

Вот это слово и исользовать: "вперёд".

2024-12-01 Клихальт

Gudleifr,
Зачем нужен переключатель?
Согласен для начала следовало бы разобраться что именно мы обсуждаем.

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

Ранее куда как чаще использовался первый вариант, сейчас этот стиль кодирования вытравливается из умов и навязывается совершенно другой.

Обсуждение вылилось в спор остроконечников и тупоконечников — в языке каким-то образом должны присутствовать оба варианта.

Мне ближе вариант чистого Си.

2024-12-01 Неслучайный читатель

уместно использовать ключевое слово then вместо fallthrough.
не уверен, что это хорошая идея. Хотя fallthrough мне тоже не нравится... Но давайте оставим выбор английского варианта названия тем, кто очень хорошо знает английский. Мне же гораздо интереснее выбрать русский вариант для этого ключевого слова.
Русское слово затем, которое, кстати, переводится на английский как then.

2024-12-02 alextretyak

Клихальт
Вот тексты первого компилятора Си, написанного на Си: https://github.com/mortdeus/legacy-cc
Вообще, судя по описанию, там два Си компилятора или две версии одного компилятора, написанные в 1972-73 гг., когда сам язык Си ещё только-только появлялся.
И всё указывает на то, что в окружении, в котором разрабатывался более ранний из них — last1120c — оператор break в switch вообще не поддерживался. Поэтому считать количество его присутствия было довольно глупо. :)(:
Так, в .c файлах в директории last1120c действительно нет ни одного break в switch. Его логика эмулируется через goto. Я решил это исправить {} и посчитать статистику снова.
Теперь количество break в switch, которые можно убрать из кода/текста программы при использовании поведения «break по умолчанию» стало 27 (против 10 в оригинальной версии).
Напомню, что псевдооператор "проваливания" потребуется лишь в 13 случаях.
Данная статистика опровергает вот это ваше утверждение:
Ранее куда как чаще использовался первый вариант
Так как 27 > 13.
Кому-то может не понравиться тот факт, что такая статистика является некой «средней температурой по репозиторию».
Вот статистика раздельно по двум этим компиляторам:
last1120c: 12 > 5.
prestruct: 15 > 8.

сейчас этот стиль кодирования вытравливается из умов и навязывается совершенно другой.
Да, кодирование в стиле «лапши из goto» с тех пор активно вытравливается. :)(: Но что в этом плохого?

И как бы это неприятно не было для вас, но я считаю, что эти полученные результаты вполне убедительно развеивают миф, что есть какой-то там более совершенный ранний стиль кодирования, при котором поведение «проваливание по умолчанию» даёт некое преимущество.

2024-12-02 Клихальт

оператор break в switch вообще не поддерживался. Поэтому считать количество его присутствия было довольно глупо. :)(:
Возможно и так, но по вашей ссылке я не увидел данного утверждения. Может поделитесь цитатой?

Да, кодирование в стиле «лапши из goto» с тех пор активно вытравливается. :)(: Но что в этом плохого?
Вытравливается не "кодирование в стиле «лапши из goto»", а его использование как таковое в любом контексте. Разве не так?

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

Кстати, камешек в копилку предыдущей темы, из обсуждения коей и вышла эта — в приведённых мною текстах компилятора (версия last1120c) программист при помощи goto эмулирует отнюдь не только break в switch, но и continue из вложенного во внешний цикл и безконечные циклы с выходом из середины. Там вообще много интересных моментов.

2024-12-04 alextretyak

Возможно и так, но по вашей ссылке я не увидел данного утверждения. Может поделитесь цитатой?
Такие мелкие детали в публичных документах, сохранившихся с того времени, обычно не описывают. И такое утверждение можно было бы поискать лишь в истории коммитов. Вот только систем управления версиями в те времена ещё не существовало. Поэтому цитаты, увы, не будет.
А вывод такой я сделал сам на основании такого факта, что в last1120c нет ни одного break в switch, а в более новом prestruct такие break есть. Значит дело не в том, что автору тех исходников «было удобнее обходиться без break в switch», а скорее всего при написании кода last1120c оператор break в switch не поддерживался [или даже не в switch, а просто break не поддерживался, т.к. в last1120c всего один break (в цикле while)], и его поддержка была добавлена позже.

программист при помощи goto эмулирует отнюдь не только break в switch, но и continue из вложенного во внешний цикл и безконечные циклы с выходом из середины.
Тоже мне, открыли Америку. Ведь в машинном коде все управляющие конструкции из языков высокого уровня реализуются через аналог goto — инструкции jmp/jcc (и даже вызов процедур и возврат при желании можно реализовать через них).

Вытравливается не "кодирование в стиле «лапши из goto»", а его использование как таковое в любом контексте. Разве не так?
Так.
Вот только приведённый вами исходный текст первого компилятора Си ни разу не аргумент в защиту использования goto в современных программах. В те времена использовали goto скорее по привычке после программирования на языке ассемблера, в котором кроме аналога goto — безусловных переходов jmp и условных jcc — по сути управляющих инструкций больше и нету (максимум, что может быть ещё — это инструкции вызова процедуры call и возврата ret).
Ну и ещё, в те времена, по причине сильнейшего дефицита памяти (как оперативной, так и постоянной/энергонезависимой), использование goto позволяло несколько сократить объём сгенерированного машинного кода. Вот только ничего хорошего в такой оптимизации я не вижу, т.к. достигнута она не столько пониманием низкоуровневых вещей и аппаратной части, а больше "трюкачеством", которое значительно затрудняет понимание исходного кода другими программистами и его сопровождение.
Но это уже оффтоп. Про goto на этом сайте есть отдельная страница.

2024-12-05 Автор сайта

alextretyak, стало интересно, а был ли break изначально в switch? В те времена фактическим стандартом Си была книга K&R. Второе издание K&R от 1988 г. стало стандартом ANSI C. Искал-искал первое издание от 1978 г. (нас же интересует, как было изначально?), и даже оглавление находил. И там было написано, что switch описан на 54 странице. Вот только саму страницу так и не нашёл. Нужны цифровые археологи. 🤣

2024-12-06 Gudleifr

Вот только саму страницу так и не нашёл
https://retrofun.pl/wp-content/uploads/sites/2/2023/12/1978-ritchie-the-c-programming-language.pdf

2024-12-07 alextretyak

Автор сайта
стало интересно, а был ли break изначально в switch?
Всё указывает на то, что да, был:
  1. Здесь указывается на различие между Си и BCPL (от которого и произошёл Си) — в последнем для break в switch использовалось специальное ключевое слово — endcase.
  2. В обоих компиляторах last1120c и prestruct оператор break поддерживается в switch наряду с while и do (поддержки оператора for в этих компиляторах ещё не было).
  3. В первом издании K&R (ссылку уже дал Gudleifr чуть выше) break в switch уже был.

2024-12-07 Клихальт

alextretyak, как видите ваше предположение об отсутствии break в switch на основании того, что его не было в исходных текстах компилятора оказалось ложным — человек писал так, как привык и как ему было удобнее.

2024-12-09 alextretyak

как видите ваше предположение об отсутствии break в switch ... оказалось ложным
Совсем не обязательно.
То, что break в switch изначально был в языке, вовсе не означает, что он был в самой первой (и неполной!) реализации языка.
Разработка компилятора — это итеративный процесс. Никто не станет пытаться реализовывать компилятор, который сразу же с самой первой своей версии поддерживает все возможности языка программирования. Для начала ограничиваются поддержкой базовых и наиболее насущных и простых в реализации операторов: if/else, goto, return. Затем можно добавить циклы (while и do-while). Реализация конструкции switch тут явно не в приоритете, как и оператора break.
И то, что оператор break реализован в самом старом из сохранившихся компиляторе last1120c (причём из написанных на Си, а не на ассемблере!), вовсе не означает, что он [оператор break] был реализован в «компиляторе, который компилировал этот компилятор [last1120c]». Скорее всего, тот компилятор вообще был написан на ассемблере. А реализовывать всякие приятные (но не обязательные) «плюшки» (вроде оператора break, который можно сэмулировать через goto) на ассемблере — это редко оправданная роскошь.
И я по-прежнему считаю, что тот «компилятор, который компилировал компилятор last1120c» break в switch [а точнее, просто break] не поддерживал. А вот более новый prestruct вполне вероятно компилировался уже посредством last1120c, поэтому break в prestruct используется достаточно активно: всего 17 break'ов, из них 15 — внутри switch.

человек писал так, как привык и как ему было удобнее.
Достоверно подтвердить или опровергнуть это может только тот, кто писал этот исходный код. Если это был действительно Деннис Ритчи (как написано в README репозитория https://github.com/mortdeus/legacy-cc), то, увы, спросить его об этом уже не получится.

2024-12-09 Неслучайный читатель

Язык C произошёл от B, который произошёл от BCPL. В "Users' Reference to B" нет ни одного случая, чтобы после case был break. А вот goto — пожалуйста.

В "A TUTORIAL INTRODUCTION TO THE LANGUAGE B" by B. W. Kernighan нашёл пример, в котором внутри цикла есть switch, а внутри switch — break. Но этот break является не концом ветви case, а выходом из цикла.¯\_(ツ)_/¯

2024-12-15 Клихальт

Неслучайный читатель, Ваши доводы подтверждают лишь то, что в языках B и BCPL такая конструкция не была предусмотрена и реализована, но разговор то шел не о них, а о C, который от этих языков хоть и произошел, но имел от них немало отличий и использование break в операторе switch одно из них.

2024-12-15 Неслучайный читатель

Язык Би появился в 1969 году, а Си — в 1972. Би делал Томсон, ему помогал Ритчи, а Си делал Ритчи, ему помогал Томсон. Си создавался для того, чтобы написать Unix. Чтобы понять, был ли break изначально или он результат эволюции и появился ближе к версии K&R, было бы хорошо посмотреть первую версию Unix.

2024-12-15 Клихальт

Неслучайный читатель,
Чтобы понять, был ли break изначально или он результат эволюции и появился ближе к версии K&R, было бы хорошо посмотреть первую версию Unix.
Да, было бы интересно взглянуть на первую версию Unix, написанную на Си. Вот только мне в сети она не попадалась. Быть может Вы знаете где её взять?

2024-12-15 Деньги на WWWетер

Первая версия Unix написана на ассемблере:
https://archive.computerhistory.org/resources/access/text/2019/09/102785108-05-001-acc.pdf

2024-12-15 Клихальт

Деньги на WWWетер, Именно поэтому я и уточнил: "написанную на Си".