Постфиксные инкремент и декремент
Операции «++» и «--» широко используются в языках с С-подобным синтаксисом.
Перед обсуждением этих операций необходимо сразу же сделать оговорку.
Здесь анализируется не принцип записи обозначения операции после операнда,
а свойство таких операций выполнять действие над операндом по окончании всех остальных операций.
В чём аномальность этих операций?
В справочных руководствах по С/С++ унарные операции «++» и «--» как в префиксном,
так и в постфиксном варианте имеют приоритет, равный 1 (всего приоритетов 16, от 0 до 15; 0 — наивысший приоритет).
Для префиксных операций это справедливо безо всяких замечаний.
Постфиксные «++» и «--» же при синтаксическом анализе так же имеют приоритет, равный 1.
А при выполнении появляется «фокус»: они выполняются в последнюю очередь, после выполнения всего выражения.
Получается, что эти операции помимо обычного существования имеют «параллельную реальность».
В алголоподобных языках операции и функции могут иметь операнды в количестве от нуля и более.
Но один операнд может принадлежать только одной операции или функции.
Но в С/С++ это не так.
«Отклонением от нормы» как раз являются «++» и «--».
Возьмём пример:
f(a++);
Аргумент «а» сначала является операндом функции «f()»,
а затем , после её выполнения, операнд «а» является операндом операции «++».
Операнд «а» один, а операций применяется к нему две!
В примере
f(++a);
такого не возникает: сначала выполняется «++», а затем результат этой операции является аргументом функции «f()».
Можно было бы предположить, что операция «++» просто имеет самый низкий приоритет.
И по этой причине она выполняется в последнюю очередь.
Опровергаем это, используя скобки для повышения приоритета:
a = (b++);
«a» будет присвоено значение «b», а не «b+1»!
Какой вывод из всего этого можно сделать?
1) Свойство постфиксных операций «++» и «--» выполняться после выполнения всего выражения противоречит общепринятому,
что операция или функция может иметь несколько операндов, но операнд относится только к одной операции или функции.
2) Эти противоречивые операции усложняют язык и компилятор соответственно;
это повод исключить подобные операции из языка, тем более что теряем не многое.
3) Удаление этих операций из языка приведёт к небольшому удлинению программ.
В примере на C вместо
do { *dst++ = *src++;} while (*src);
придётся писать:
do { *dst = * src; ++dst; ++src;) while (*src);
Это не очень дорогая расплата.
Зато отсутствуют «фокусы» и «трюки».
Есть ещё одна причина «не любить» эти операции.
Рассмотрим переопределение операции «++» в C++:
BigInt& operator++(BigInt& op1, BigInt op2) { ... }; // Это постфиксный инкремент
BigInt& operator++(BigInt& op1) { ... }; // А это префиксный инкремент
Чем отличаются эти определения?
Постфиксный инкремент имеет дополнительный операнд, единственное назначение которого —
сигнализировать о том, что инкремент постфиксный, а не префиксный.
Но средствами языка невозможно повторить такой трюк для, допустим, префиксного унарного минуса.
И для любой другой операции.
Это «зашито» в язык «шаманским» образом.
Но в языке не должно быть «магии», всё должно быть логичным и подчиняться общим правилам!
Меньше шаманства — меньше танцев с бубнами.
И третья причина «не любить» постфиксные «++» и «--».
Вы обратили внимание, что синтаксис этих операций при объявлении и употреблении разный?
Постфиксные «++» и «--» — это унарные операции, но объявляются как бинарные.
Опять нелогичность!
Операции и функции в языке должны и объявляться, и употребляться одним и тем же способом!
Но последнее замечание относится к реализации этих операций в C-подобных языках.
Вновь разрабатываемые языки могут избежать подобного недостатка.
Опубликовано: 2012.09.25, последняя правка: 2014.12.23 13:37
Отзывы
✅ 2014/04/26 11:45, Utkin #0
Как сторонник Паскаля, вообще не вижу смысла в подобных операциях. Есть вполне себе работающее а=а+1. Для чего городить огород. Сам факт существования Паскаля и его использование для написания вполне рабочих программ говорит о том, что данные операции не обязательны вообще. Можно рассматривать как частный случай синтаксического сахара.✅ 2014/05/01 15:05, Автор сайта #1
Это краткая и удобная запись, позволяющая записывать имя объекта только единожды. Поэтому она и популярная, несмотря на свою «антинаучность». В Хаскеле нет таких операторов, но для императивных языков они удобны.✅ 2014/06/17 04:44, 87.241.204.6 #2
Это краткая и удобная запись, позволяющая записывать имя объекта только единожды И источник путаницы. На современном этапе основная проблема не в написании программы (при желании можно все очень сильно автоматизировать), а в её отладке, тестировании и дальнейшем сопровождении. Если говорить об одиночных разработках, то на данный факт можно закрыть глаза. Если говорить о промышленном программировании, то это источник бед. Я крайне негативно отношусь к смешиванию различных видов записи подобных операций. Есть удобное "человеческое" представление, большой практический опыт показал, что эксперименты на программистах с/с++ закончились неудачей. Программирование не должно коробить мыслительные процессы (итак алгоритм переводится на язык программирования, так ещё в самом языке для понимания кода требуются дополнительные преобразования). Все эти переводы из одной системы представления в другую источник ошибок. Если же форма записи привлекательна, то должна быть единственной во всей программе и никак иначе.✅ 2014/06/17 16:16, Автор сайта #3
А какую запись Вы считаете человеческой? А := А + 1 — это лучше, чем ++ А? И с какой точки зрения запись «А := А + 1» лучше — с точки зрения школьника? Когда я был школьником, то вообще не представлял, что делает операция «&». Но вуз помог потом. Теперь понимаю, что делают «&» и «++». Они по своей сложности не очень отличаются.✅ 2014/06/19 10:02, utkin #4
Нет, я о том, что должна быть единая форма записи без смесей. И желательно как можно ближе к естественной форме записи. А:=А+1 совершенно не обязательно. Можно и А=А+1.✅ 2014/06/19 17:47, Автор сайта #5
В функциональном программировании вообще предпочитают не модифицировать значения имеющихся объектов, а создавать новые объекты на основании старых:В = А + 1 Так легче раскрутить цепочку вычислений на предмет её доказательной правильности.
Лично для меня инкремент столь же естественен, как и А + 1, ещё со времён Clipper и Forth. Си был для меня третьим языком, где я увидел инкремент :)✅ 2014/06/23 09:25, utkin #6
Ну вот опять Ваши личные предпочтения. Вы же не один будете программировать на новом языке, или один? Ориентироваться на Вас (как и на любого другого) заведомо провальное дело. Ориентироваться нужно на большинство. А большинство вполне естественно воспринимает А=А+1, а вот насчет а++ будет путаница. Именно из-за сочетания обоих вариантов. Также как такой инкремент порождает ошибки в C/C++ где есть и ++а и а++ и а=а+1. В комбинации со скобками это создает вырвиглазные выражения. И если один выпендрился, то остальные путаются. Для промышленных масштабов это абсолютно не приемлемо. Код должен быть ясен большинству участников проекта, а не только его создателю.✅ 2014/06/23 13:48, Автор сайта #7
Большинство таки приемлет инкремент и декремент. Это личные предпочтения всех, кто программирует на Си-подобных языках: C, C++, C#, Objective C, D, Java, Cyclone и т.д. Если заглянуть в рейтинг Tiobe, то суммарный рейтинг этих языков — 2/3, т.е. «конституционное большинство». Каков процент тех, кому нравится — трудно сказать, но они его приемлют — это точно. Иначе бы они не пользовались этими языками. Вам, как поклоннику Паскаля, это не нравится. Но что поделаешь, когда речь идёт о большинстве.✅ 2014/06/30 12:23, utkin #8
Это личные предпочтения всех, кто программирует на Си-подобных языках: C, C++, C#, Objective C, D, Java, Cyclone и т.д. Я не согласен с Вами, так как следует разделять программирование «для себя» и промышленное программирование. В последнем случае язык навязывает работодатель — не попрётесь же с Паскалем там где пишут на C/C++. Поэтому тут тонкости насчет «личных предпочтений».✅ 2014/06/30 17:28, Автор сайта #9
Похоже, Вам трудно представить, что инкремент и декремент могут употреблять добровольно. Был такой «всесоюзный староста» Михаил Иванович Калинин, который пить не любил, но ему приходилось это делать, чтобы не быть белой вороной в когорте сталиных, молотовых, кагановичей. Он недоумевал по поводу водки: «Как её могут беспартийные пить?». В его голове не укладывалось, что водку можно пить в удовольствие, а не по долгу службы. Между прочим, в C++ ничто не препятствует отказу от инкремента и декремента. Возьмём такой код:int array[10]; int* ptr = array; ++ ptr; *ptr = 99; // array[1] теперь равен 99 ptr = ptr + 1; // это по-паскалевски! *ptr = 98; // array[2] теперь равен 98 Т.е. программированию без инкремента нет препятствий. Но программистам они нравится, они предпочитают более короткий код. Поэтому пользуют его и в хвост, и в гриву.
Кстати, о большинстве. Где-то читал примерно такой совет: «Если хотите достичь своей цели, никогда не ориентируйтесь на большинство. Иначе бы мы до сих пор жили на земле, которая покоится на трёх китах, не открыли бы Америки и не летали бы в атмосфере и космосе — ведь это невозможно».✅ 2015/02/11 17:19, Автор комментария #10
Т.е. я так понял из комментариев, что никто не знает операцию inc в Паскале?✅ 2015/02/11 20:40, Автор сайта #11
Автор Паскаля Никлаус Вирт выступал против Си-шных операций инкремента/декремента.✅ 2015/09/03 14:49, Александр #12
Отличный урок How to по работе инкремента и декремента в C# — https://www.youtube.com/watch?v=FTuLlHBRZxs✅ 2016/04/29 04:29, Олег #13
Utkin:Сам факт существования Паскаля и его использование для написания вполне рабочих программ говорит о том, что данные операции не обязательны вообще. Можно рассматривать как частный случай синтаксического сахара. Ты серьезно?! Сам факт СУЩЕСТВОВАНИЯ Паскаля?! Что-то Вас занесло, уважаемый ))✅ 2016/04/29 11:30, Автор сайта #14
А ещё сам факт существования Brainfuck говорит о том, что в языке программирования можно обойтись без букв.✅ 2016/05/05 14:18, utkin #15
Ты серьезно?! Сам факт СУЩЕСТВОВАНИЯ Паскаля?! Что-то Вас занесло, уважаемый )) Конечно. Он существует: а) давно, б) используется для обучения, в) используется в реальных проектах, г) имеет также и всяческие ответвления навроде языка Дельфи.
Все это говорит о том, что операции вида а++ НЕОБЯЗАТЕЛЬНЫ для использования. То есть синтаксический сахар. Никакого преимущества они не дают. Вопрос в эстетике — кому-то нравится или легче воспринимается, а для кого-то это противоестественная запись.✅ 2016/05/05 14:19, utkin #16
А ещё сам факт существования Brainfuck говорит о том, что в языке программирования можно обойтись без букв. А он используется в реальной работе?✅ 2016/05/05 14:36, Автор сайта #17
Просто я хотел сказать, что сам факт существования в данном случае ничего не значит.✅ 2016/08/06 00:11, Александр #18
1. Постфиксный инкремент/декремент имеют приоритет 1, префиксный инкремент/декремент имеют приоритет 2. 2. Приоритетов 18. 3. Не каждая функция примет аргумент (a++), в связи с тем, что постфиксные инкремент/декремент порождают временную переменную не LVALUE. Таким образом, следующая функция вызовет ошибку при попытке передать ей a++void f(int& val){/* */}; 4. Попробуйте скопировать простую строку "тест\0" предложенным Вами кодомdo { *dst = * src; ++dst; ++src;) while (*src); И последнее. Возможно, у постфиксных инкремента/декремента должны быть подобные свойства?✅ 2016/08/23 03:59, xDDGx #19
Почему бы тогда не оставить постфиксные ин/декремент, но дать им минимальный (разумный) приоритет? Никакой магии, а возможность написатьdo { *dst++ = *src++;} while (*src); останется.
Почему-то никто не вспомнил ещё про такую конструкцию: a+=1. Длиннее, чем ++a, но короче a=a+1, да и функциональнее первого (шаг может быть и 2, и 3, и нецелым, и выбор операций больше — даже сдвиги — а не только +/-).
Александр, если вы имеете в виду, что не копируется \0, то исходным do { *dst++ = *src++;} while (*src); достигается то же самое, их функционал эквивалентен. В конце концов, это всего лишь пример...✅ 2016/10/31 08:43, rst256 #20
Теперь понимаю, что делают «&» и «++». Они по своей сложности не очень отличаются. Может, вы знаете что они делают «&» и «++» в указанном ниже коде? void iseq(int a, int b){ assert(a==b); } void ismt(int a, int b){ assert(a+1==b); }
int main(int argc, char **argv){
{ int j=100, *jp; jp=&j; iseq(j++, j); assert(j==101); } //100, 100 { int j=100, *jp; iseq(j++, j); jp=&j; assert(j==101); } //100, 100 { int j=100, *jp; iseq(j++, j); assert(&j && j==101); } //100, 100 { int j=100, *jp; ismt(j++, j); assert(j==101); } //100, 100
{ int j=100, *jp; jp=&j; printf("\n%d, %d\n", j++, j); assert(j==101); } //100, 100 { int j=100, *jp; printf("%d, %d\n", j++, j); jp=&j; assert(j==101); } //100, 100 { int j=100, *jp; printf("%d, %d\n", j++, j); assert(&j && j==101); } //100, 100 { int j=100; printf("%d, %d\n", j++, j); assert(j==101); } //100, 101
{ int j=100, *jp; jp=&j; printf("\n%d, %d\n", ++j, j); assert(j==101); } //101, 100 { int j=100, *jp; printf("%d, %d\n", ++j, j); jp=&j; assert(j==101); } //101, 100 { int j=100; printf("%d, %d\n", ++j, j); assert(j==101); } //101, 101 return 0; } ✅ 2017/08/03 12:30, Алексей #21
Написано:Аргумент «а» сначала является операндом функции «f()», а затем , после её выполнения, операнд «а» является операндом операции «++». Проблема в словах «после её выполнения». Это следует исправить на:Аргумент «а» копируется в операнд функции «f()», затем, до выполнения тела функции, над «а» производится «++», и затем выполняется тело функции «f()» с исходным операндом. Иначе говоря, на момент исполнения кода «f()», над «а» уже произведен постфиксный инкремент, хотя операнд она получит не инкрементированный.
Именно таково поведение языка C++ по стандарту: C++ standard, Program execution 1.9.15:, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2798.pdf
А также языка C. Это также проверяется поведением реальных компиляторов. Причём описанная ситуация становится сложнее. И, вообще говоря, поведение не определено в случае f(a++, a++); Здесь запятая «,» не является оператором запятой.✅ 2022/04/29 15:34, stein47 #22
Использование инкремента и декремента очень удобно. Но чревато "фокусами" и ошибками. Причем источником ошибок является программист, а не компилятор. Компилятор разберет и префиксный и постфиксный вариант без проблем. Нам нужно, сохранив удобство, снизить вероятность ошибок. Предложен вариант отказаться от постфиксного инкремента/декремента. Лично я почти всегда пользуюсь постфиксной записью. И даже не потому, что мне нужен "магический" отложенный эффект, а просто такая запись более эргономична. a++; ++b; Первая запись банально удобнее при наборе, а в данном контексте смысл равнозначен. Подвожу свою мысль к завершению: можно оставить только постфиксную запись, но смысл приравнять к префиксной. Правда, это уже разрыв "сишного наследия". И это "наследие" снова станет потенциальным источником ошибок...✅ 2022/04/29 15:47, Gudleifr #23
Но чревато "фокусами" и ошибками Все "нормальные языки" содержат в себе два практически непересекающихся языка: операторов и выражений. И для языка выражений до сих пор действует правило скриптов — любой возможной лексеме нужно приписать полезное значение, дабы вместить в одну строку как можно больше финтов. По идее, языковое творчество должно бы идти как раз по пути выделения языка операторов и обеспечения возможности переключения с одного языка выражений на другой на лету.✅ 2022/04/29 16:18, Автор сайта #24
не потому, что мне нужен "магический" отложенный эффект, а просто такая запись более эргономична. можно оставить только постфиксную запись, но смысл приравнять к префиксной. Правда, это уже разрыв "сишного наследия". Совершенно с Вами согласен. Правда, эти операторы с точки зрения функционального программирования изменяют состояние, а надо бы стремиться к иммутабельности.✅ 2024/03/27 00:00, alextretyak #25
И всё-таки, иногда постфиксные операции значительно удобнее. Вот у меня есть такой метод в классе: uint8_t read_byte() { return buffer[buffer_pos++]; } Если отказаться от постфиксного инкремента, тогда придётся этот метод переписать так: uint8_t read_byte() { uint8_t result = buffer[buffer_pos]; ++buffer_pos; return result; } Мало того, что строчек кода в теле метода стало в три раза больше, так ещё и пришлось вводить лишнюю сущность и давать ей имя (временная переменная result)...., а свойство таких операций выполнять действие над операндом по окончании всех остальных операций. А если лишить эти операции такого свойства? Что, если постфиксные инкремент и декремент всегда будут определены таким образом:template <typename T> T operator++(T& a, int) { T temp = a; // для типа `T` должен быть определён конструктор копирования ++a; // для типа `T` должен быть определён префиксный оператор `++` return temp; } // и аналогично для `--` В этом случае появляется сразу несколько плюсов:- поведение постфиксных операций становится полностью детерминированным: выражение a++ − a++ всегда возвращает -1 и при этом увеличивает a на 2; правда при условии, что компилятор при расчёте разности вычисляет уменьшаемое (левый операнд) всегда перед вычитаемым (правый операнд);
- исчезает различие в поведении постфиксных операций для встроенных в язык типов (int, float и др. — для них «++» и «--» выполняются после выполнения всего выражения) и для пользовательских типов (для них применяются переопределённые постфиксные «++» и «--»);
- пропадает «магия» в виде дополнительного неиспользуемого аргумента при переопределении операций «++» и «--», т.к. программист вообще не может переопределить постфиксные «++» и «--», а только префиксные (постфиксные всегда генерируются компилятором автоматически);
- это упрощает реализацию, т.к. запись a++ компилятор может просто заменять на вызов служебной функции postfix_increment(a), которая реализована аналогично:
template <typename T> T postfix_increment(T& a) { T temp = a; ++a; return temp; } Ну и в качестве оптимизации, считаю, что операция a++ должна автоматически заменяться компилятором на ++a в том случае, когда результат операции не используется. Всё-таки постфиксный инкремент выглядит красивее префиксного. Неспроста же Бьёрн назвал язык C++, а не ++C. :)(:✅ 2024/03/27 08:01, kiv #26
ИМХО обсуждение Си и Си++ как одного языка в корне не корректно! Если при написании кода на Си Вы не представляете в голове хотя бы приблизительно, какой машинный код будет сгенерирован, или как минимум никогда не интересовались для общего понимания ассемблерным листингом компиляции, то постарайтесь исключить Си из своих инструментов и перестаньте обсуждать его достоинства и недостатки.✅ 2024/03/27 16:08, Автор сайта #27
постарайтесь исключить Си из своих инструментов и перестаньте обсуждать его достоинства и недостатки. Знаете, Вас никто не называл земляным червяком, поэтому можно обсуждать идеи, ничего не рекомендуя (как учил Жванецкий) участникам дискуссии. Ведь высказывание было по теме. Поэтому надо критиковать смысл сказанного, а не людей.
Если сделать одинаковой семантику префиксных и постфиксных инкрементов и декрементов, то одного из видов можно избавиться. Выгоднее избавиться от постфиксных, чтобы избавить язык от всех постфиксных операций, от этого упрощается синтаксис языка. Хотя постфиксные инкременты и декременты можно оставить как синтаксический сахар к префиксным.
Хотя, конечно, жаль, что больше не будет гениальной по лаконичности записи do { *dst++ = *src++;} while (*src); Есть ещё одна вещь, которая против шерсти не соответствует традициям Си. Выскажу крамольную мысль, что изменение какого-либо значения в одном флаконе с его использованием в качестве аргумента не есть хорошо. Процитирую Википедию, статью «Синтаксис и семантика языка Си»:В следующем примере переменная изменяется трижды между точками следования, что приводит к неопределённому результату: int i = 1; // Описатель - первая точка следования, полное выражение - вторая i += ++i + 1; // Полное выражение - третья точка следования printf("%d\n", i); // Может быть выведено как 4, так и 5 Другие простые примеры неопределённого поведения, которого необходимо избегать: i = i++ + 1; // неопределённое поведение i = ++i + 1; // тоже неопределённое поведение
printf("%d, %d\n", --i, ++i); // неопределённое поведение printf("%d, %d\n", ++i, ++i); // тоже неопределённое поведение
printf("%d, %d\n", i = 0, i = 1); // неопределённое поведение printf("%d, %d\n", i = 0, i = 0); // тоже неопределённое поведение
a[i] = i++; // неопределённое поведение a[i++] = i; // тоже неопределённое поведение ✅ 2024/03/27 19:12, MihalNik #28
избавить язык от всех постфиксных операций, от этого упрощается синтаксис языка Постфиксные операции не усложнят синтаксис и семантику, если не будут возвращать значений.изменение какого-либо значения в одном флаконе с его использованием в качестве аргумента не есть хорошо Вот и я про тоже. Да, краткость хороша, но в какой-то мере, иначе код придется дольше разглядывать и разгадывать. Перл тому отличный пример.Если отказаться от постфиксного инкремента, тогда придётся этот метод переписать так Есть ещё один "крамольный" вариант — разрешить операторы после "return", тогда строки будет две, а не три, без лишних переменных. А return можно переименовать в result.✅ 2024/04/01 14:10, veector #29
И всё-таки, иногда постфиксные операции значительно удобнее. Вот у меня есть такой метод в классе: uint8_t read_byte() { return buffer[buffer_pos++]; } Когда я вижу такой код на ревью (проверке), то сразу выдаю минус в карму.✅ 2024/04/01 14:27, veector #30
Есть ещё один "крамольный" вариант — разрешить операторы после "return", тогда строки будет две, а не три, без лишних переменных. А return можно переименовать в result. MihalNik, кстати, это один из самых правильных вариантов, даже в одном известном языке применяется, но нынче не очень популярном.✅ 2024/04/02 00:11, Автор сайта #31
buffer_pos++ разрешить операторы после "return", тогда строки будет две, а не три, без лишних переменных. В приведённых выше примерах есть неясность. Если buffer_pos локальная, то вообще непонятно, зачем её увеличивать перед выходом из функции, ведь её значение не возвращается оператором return. Если она глобальная, то смысл увеличения есть, но есть резонный вопрос — зачем она глобальная?!
Есть ещё вариант, когда buffer_pos — волатильная, тогда изменять её могут для произведения побочного эффекта. Но этот вариант вообще ни в какие ворота не лезет.✅ 2024/04/04 00:00, alextretyak #32
MihalNikЕсть ещё один "крамольный" вариант — разрешить операторы после "return", тогда строки будет две, а не три, без лишних переменных. А return можно переименовать в result. Хороший вариант, согласен. Жаль только, что не так много языков программирования, которые поддерживают специальную переменную result.
Автор сайтаЕсли buffer_pos локальная Как же она может быть локальной, когда в теле метода она не объявляется? Да, приведённая строка кода является полным телом метода read_byte() .Если она глобальная, то смысл увеличения есть, но есть резонный вопрос — зачем она глобальная?! Из моих слов «такой метод в классе» можно было догадаться, что в коде речь идёт о переменных-членах класса. И buffer , и buffer_pos являются переменными-членами.
veectorто сразу выдаю минус в карму. Просто выдаёте и всё? А конкретные советы/рекомендации (о том, какой код был бы лучше в данном случае) Вы не даёте из принципа, я так полагаю. 🤣даже в одном известном языке применяется, но нынче не очень популярном. Почему Вы так не любите конкретику? Что мешало сразу просто указать название этого языка программирования, как будто это прям какая-то коммерческая тайна.
Из более-менее популярных языков программирования, которые поддерживают специальную переменную для возврата значения, я могу назвать только BASIC, Pascal/Delphi и Nim (причём в первых двух эта переменная является именем функции, а в последнем используется специальная переменная result ). Но в C++ такой возможности нет, а потому вопрос «а как более правильно реализовать return buffer[buffer_pos++]; » остаётся открытым.✅ 2024/04/05 12:36, veector #33
alextretyak, не, я добрый, всегда, всем, все разъясняю.✅ 2024/04/07 00:00, alextretyak #34
всегда, всем, все разъясняю. Ну, в таком случае, хотелось бы услышать (хотя бы краткое) разъяснение, что именно вас не устраивает в процитированном коде и какой код был бы лучше в данном случае?✅ 2024/04/08 17:52, veector #35
alextretyak, ну тут всё же просто: Вы же считываете значение из массива, 1 байт, не проверяя номер элемента.✅ 2024/04/08 23:43, Автор сайта #36
alextretyak, поделитесь тайным знанием, почему ваши сообщения в последнее время всегда делаются в 00:00 🙃✅ 2024/04/10 00:00, alextretyak #37
veectorВы же считываете значение из массива, 1 байт, не проверяя номер элемента. Ах, вот оно в чём дело. Просто название обсуждаемой статьи — «Постфиксные инкремент и декремент», поэтому я решил немного сократить тело метода read_byte() для наглядности. Тем более, что в C++ такой код вполне может быть допустим в том случае, когда buffer — это не просто указатель или массив в стиле Си, а объект-экземпляр класса массива с контролем выхода за границы. Тогда внутри перегруженного operator[] будет та самая «проверка номера элемента», о которой вы говорите и которую в языке Си пришлось бы вставлять в код явно.
Вообще, полный код метода read_byte() , который используется в реальном проекте (https://github.com/alextretyak /file_for_humans/blob /master/IFile.hpp), выглядит так: uint8_t read_byte() { if (at_eof()) throw UnexpectedEOF(); return buffer[buffer_pos++]; } Метод at_eof() , несмотря на название, не только проверяет на конец файла, а очень много чего делает: аллоцирует buffer , если он ещё не был проаллоцирован, читает из файла данные в buffer , если buffer_pos указывает на конец буфера, и при этом сбрасывает buffer_pos в 0 и обновляет позицию начала буфера в файле для корректной работы метода tell() , а если прочитать файл не удалось, то порождает исключение (таким образом, к моменту выполнения кода buffer[buffer_pos++] содержимое buffer уже подготовлено и buffer_pos гарантированно находится в допустимых пределах). Но даже такое тело метода вполне можно впихнуть в один return: uint8_t read_byte() { return !at_eof() ? buffer[buffer_pos++] : throw UnexpectedEOF(); } Просто я не люблю использовать throw в выражениях, поэтому и не стал так писать в реальном коде. Но возвращаясь к теме статьи: против использования постфиксного инкремента в теле метода read_byte() вы ничего не имеете?
Автор сайтаподелитесь тайным знанием, почему ваши сообщения в последнее время всегда делаются в 00:00 Ну, не сказать, чтобы в «последнее». Уже более 4-х с половиной лет, начиная с этого сообщения. 😁
А тайного знания тут никакого нет: просто я давно заметил, что если отправлять сообщения не сразу же после написания, а отложить их отправку/публикацию хотя бы на несколько часов и периодически перечитывать текст перед отправкой, то качество сообщений при этом повышается. Мозг в фоновом режиме вспоминает какие-то дополнительные детали/уточнения, которые так и просятся добавить в сообщение в процессе его перечитывания. Если в сообщении были какие-то излишне эмоциональные/резкие высказывания, то по прошествии времени это становится хорошо заметно и получается либо перефразировать их в более конструктивном ключе, либо появляется решимость вообще вырезать/удалить их из сообщения. Также ошибки/опечатки в тексте лучше обнаруживаются и исправляются.
Почему я выбрал время отправки сообщений именно 00:00 по Москве? Ну, с одной стороны, в этом есть что-то по-программистски красивое. А с другой, это оказалось ещё и очень удобное для меня время: во Владивостоке это 7:00 утра, и я успеваю на свежую голову ещё разок хорошенько обдумать сообщение перед отправкой.
Если интересует техническая сторона вопроса, то никакими скриптами/ботами я не пользуюсь. Просто сверяю системное время с https://time.is и нажимаю кнопку отправки сообщения примерно в 7:00:30. Вероятность того, что время на сервере, куда я отправляю сообщение, расходится с time.is более чем на 30 секунд очень мала, поэтому пока что получалось отправлять сообщения без ошибок точно в 00:00.✅ 2024/04/10 17:55, veector #38
alextretyak, я не занимаюсь разработкой языков и компиляторов, хотя потенциально умею это делать. Т.е. я как бы являюсь пользователем языков программирования и компиляторов, поэтому, мои ответы стоит воспринимать как "просто скромное мнение одного из пользователей".
К конструкциям языка вида "var++" и "--var" у меня очень простое отношение, как к удобному инструменту (типа "синтаксического сахара"), а не как к смысловой части языка (и/или компилятора). Соответственно, как любой инструмент, его можно приметь в дело и не в дело.
Мой критерий применения инструмента также простой — инструмент не должен усложнять программу. Т.е. любой другой программист, кто будет читать программу, не обязан разбираться и помнить тонкости, потому что это вредит конечной цели — логике работы программы.
Вот так я НЕ делаю: do { *dst++ = *src++;} while (*src); А вот так делаю: do { *dst = *src; dst++; src++; ) while (*src); Но возвращаясь к теме статьи: против использования постфиксного инкремента в теле метода read_byte() вы ничего не имеете? Я не против постфискного и префиксного инкрементов, но считаю, что в теле метода read_byte() в виде buffer[buffer_pos++] он неуместен, а программа, которой вместо простой проверки границ приходится отлавливать исключения — плохо спроектирована (не обижайтесь, но это мое мнение). Как бы ни было принято большинством в мире, наличие в тексте программы исключений и ассертов рантайма, для меня это признаки плохо спроектированной программы.
Вместо чтения read_byte() и буфера я использую другие методы и понятия: поток и извлечение информации из потока.// Простите, но я люблю Си, поэтому, будет чистый Си, а в C++ вы уж сами переведете. bool stream_get_byte(stream_t *stream, uint8_t *byte_ptr); int stream_get_byte(stream_t *stream); // + #define STREAM_EMPTY (-1), результат вне кодировки байта.
// К буферам это всё тоже применимо и тоже использую: bool buffer_get_byte(buffer_t *buffer, uint8_t *byte_ptr); int buffer_get_byte(buffer_t *buffer); // + #define BUFFER_EMPTY (-1), результат вне кодировки байта. Причем, слово get означает извлечение байта и это, на мой скромный взгляд, очень правильная по смыслу и достаточно частая операция с потоками и буферами.Просто я не люблю использовать throw в выражениях, поэтому и не стал так писать в реальном коде. Мое отношение к любым исключениям строго негативное. Исключения этот как параллельная вселенная ко всей логике программы. При кажущейся простоте применения исключений в тексте, человеку очень трудно спроектировать алгоритм правильно с учетом работы этих исключений потому что они нарушают порядок выполнения алгоритма программы и программа зачастую ведет себя слишком не предсказуемо (не предусмотрено программистом).
Так исторически сложилось, что я больше пишу на Си с применением парадигмы ООП и мне ни разу не потребовалось применять исключения ни в одном крупном проекте. Крупным я считаю проект, состоящий из десятков разнотипных взаимодействующих программ, с общим числом запущенных экземпляров около сотни и все программ созданы с применением парадигмы ООП. Ибо парадигма ООП не зависит от языка и больше относится к архитектуре программы, а текст программы можно делать на любом языке (хоть на C++, хоть на Си и ассемблере), это просто синтаксис самого C++ сделан в парадигме ООП.
PS. Да простит меня Автор сайта за англицизмы, но они точно отражают мою мысль и я считаю, что их использование уместно и никак не ущемляет русский язык.✅ 2024/04/11 00:08, Автор сайта #39
Ах, вот оно в чём дело. Вот поэтому не стал вмешиваться в дискуссию, потому что вариантов было много, и какой из них имелся в виду — знали только Вы.Уже более 4-х с половиной лет Только недавно обратил внимание.если ... отложить их отправку/публикацию ... и периодически перечитывать текст перед отправкой, то качество сообщений при этом повышается. Без сомнения. Но тогда голова занята ответом. А ответы не всегда хочется давать, потому что они неоднократно давались, а одни и те же вопросы всё равно задаются и поднимаются. А на это уходит драгоценное время, которое могло бы быть потречено с большей пользой. Но Ваше мнение всё равно приветствуется. 🤣наличие в тексте программы исключений и ассертов рантайма, для меня это признаки плохо спроектированной программы. Джоэл Спольски справедливо указывает, что исключения значительно хуже, чем «goto». Впрочем, можно обойтись без них, решения есть.✅ 2024/04/21 00:00, alextretyak #40
veectorТак исторически сложилось, что я больше пишу на Си с применением парадигмы ООП и мне ни разу не потребовалось применять исключения ни в одном крупном проекте. Мне, откровенно говоря, тоже. Самый крупный мной разработанный проект содержал порядка 30 тыс. строк кода на C++ — графический движок (3D-рендеринг, а также 2D GUI) для одной не слишком известной компьютерной игры. В коде как движка, так и самой игры (не считая серверную часть) не использовались ни исключения, ни динамическая идентификация типов (RTTI), ни STL и практически не использовалась даже crt. И весь код (не считая серверную часть) был написан на C++03, т.к. в 2009 году C++11 ещё не было.
Но, тем не менее, я бы не взял на себя смелость утверждать, что использование исключений, RTTI и STL — это признаки плохо спроектированной программы.
Я вполне допускаю, что во всех проектах, над которыми вы работали, исключения были малополезны, либо даже вредны. Но давайте не будем обобщать свой личный опыт на всё программное обеспечение в мире. В конце концов, всё зависит от задачи, а задачи бывают разные.
После того, как я познакомился с Python, моё мнение об исключениях значительно изменилось. Если в C++ неотловленное в рантайме исключение выдавало совершенно невнятную ошибку, непонятную ни пользователю, ни программисту, то в Python сразу было понятно, где и в чём проблема. В простой консольной программе на Python можно вообще не проверять ошибки открытия файлов, т.к. если функция open() не смогла открыть файл и вызвала необработанное исключение, то в консоли отобразится не только понятный тип ошибки (FileNotFoundError) и полный стек вызовов (call stack) с указанием строк кода, но и само имя файла, который пытались открыть.
С другой стороны, накладные расходы на исключения в современных компиляторах C++ сократились многократно. И теперь можно быть практически уверенным в том, что если исключения не срабатывают, то код работает так же быстро как если исключения вообще выключить опцией компилятора.
И хотя реализация идеи о том, что на компилируемых языках можно писать надёжный и эффективный код, в котором бы не нужно было вставлять проверки на каждый чих (например, на каждый вызов функции чтения блока данных из файла), ещё требует доработки, но это не значит, что в этом направлении вообще не нужно двигаться. Собственно, представленный мной код метода read_byte() , входящий в состав ffh — библиотеки для удобного, безопасного и эффективного чтения файлов, и является моей попыткой двигаться в этом направлении.Вместо чтения read_byte() и буфера я использую другие методы и понятия: поток и извлечение информации из потока. Хорошо. Тогда давайте я буду использовать понятие «извлечение информации из буферизованного потока». Думаю, вы согласитесь, что файл можно рассматривать как частный случай потока. У которого, в отличие от потока, известен размер и который поддерживает установку позиции чтения на произвольное место в файле.
Зачем нужна буферизация, особенно при побайтовом чтении? Дело в том, что каждый вызов ReadFile() (если говорить про Windows) или read() (в POSIX) осуществляет переход в ядро и обратно (порядка 3000 тактов) и вызывать ReadFile() для чтения из файла маленькими блоками будет крайне неэффективно. По моим замерам каждый вызов ReadFile() для чтения всего одного байта обходится более чем в 8000 тактов даже при условии, что читаемый файл уже содержится в кэше операционной системы. Что мешает читать файл большими блоками или вообще сразу целиком в память? Первое не всегда удобно, а второе — не всегда приемлимо.
Допустим, у нас есть многогигабайтный текстовый файл и мы хотим не загружая его в память целиком подсчитать в нём количество строк в стиле Windows, т.е. сколько раз в файле встречается пара символов "\r\n". Если бы мы считали просто количество одиночных символов (количество '\n', например), тогда можно было бы читать файл большими блоками и просто подсчитывать количество байт с кодом символа '\n' внутри каждого прочитанного блока. Но в случае "\r\n" так просто уже не получится, т.к. возможны ситуации, когда прочитанный блок оканчивается на символ '\r', а для того, чтобы узнать следующий символ, необходимо прочитать следующий блок из файла. При этом, необходимо запомнить, что предыдущий блок оканчивался на символ '\r'. Данный алгоритм можно выразить в следующем коде. int lines_count = 0; int fd = open("input.txt", O_RDONLY | O_BINARY); bool cr = false; static char buffer[32*1024]; while (true) { int n = read(fd, buffer, sizeof(buffer)); if (n <= 0) break;
if (cr && buffer[0] == '\n') lines_count++;
for (int i = 1; i < n; i++) if (buffer[i] == '\n' && buffer[i-1] == '\r') lines_count++;
cr = buffer[n-1] == '\r'; } std::cout << lines_count << '\n'; // или printf("%i\n", lines_count); При использовании же метода read_byte() данная задача решается так: int lines_count = 0; IFile f("input.txt"); // FILE *f = fopen("input.txt", "rb"); char prevc = 0; // char prevc = 0, c; while (!f.at_eof()) { // while ((c = fgetc(f)) != EOF) { char c = (char)f.read_byte(); // (в этой строке будет пусто) if (c == '\n' && prevc == '\r') lines_count++; prevc = c; } std::cout << lines_count << '\n'; Разумеется, можно решить эту задачу аналогично с использованием стандартных файловых потоков Си (код я привёл в блоке комментариев справа). Но суть в том, что такой код значительно проще (тело цикла получилось в два раза короче) при сопоставимой производительности за счёт буферизации чтения.
Если попытаться решить эту задачу с помощью отображения файла в память, то код получится не слишком проще, а производительность существенно ниже (хотя это сильно зависит от операционной системы).считаю, что в теле метода read_byte() в виде buffer[buffer_pos++] он [постфиксный инкремент] неуместен Но как тогда, по-вашему, следовало бы реализовать этот метод? Или даже, давайте я напишу код в вашем стиле:int buffered_stream_get_byte(buffered_stream_t *stream) { if (buffered_stream_is_empty(stream)) return STREAM_EMPTY; return stream->buffer[stream->buffer_pos++]; } Здесь вы также считаете неуместным использование постфиксного инкремента? Тогда какой код был бы лучше в данном случае?✅ 2024/07/08 16:15, veector #41
alextretyak, прошу прощения, за долгое отсутствие ответа. Первоначальный, который я быстро написал, мне не понравился, потом дела, заботы... даже немного позавидовал вам, что хватает времени и сил писать такие развернутые ответы и рабочий код для них.Здесь вы также считаете неуместным использование постфиксного инкремента? Да.Тогда какой код был бы лучше в данном случае? Мой ответ вам вряд ли понравится, т.к. в нем больше строк кода, есть эта промежуточная переменная, которая вам не нравится ибо можно обойтись без нее, и ещё этот инкремент неприятно размещен на отдельной строке, когда его можно легко разместить внутри чтобы сэкономить строчки. Но постараюсь объяснить и защитить свою позицию.
Я модифицировал вашу краткую функцию вот таким образом:int buffered_stream_get_byte(buffered_stream_t *stream, int default_value) { int result = default_value; if (!buffered_stream_is_empty(stream)) { result = stream->buffer[stream->buffer_pos]; stream->buffer_pos++; };
return result; } Оптимальный код писать конечно же надо, это однозначно. Но оптимизация это все-таки не про сокращение числа строчек в программе методом return stream->buffer[stream->buffer_pos++] , а про ООП, информационные модели, интересные приемы и архитектурные решения. Код программы — это же не константа, он постепенно изменяется. И уж лучше пусть программист подумает над тем, что неплохо бы было, чёрт возьми, не слепо следовать исходным условиям задачи, а предусмотреть параметр типа default_value для того, чтобы буфер подходил в будущем для ещё большего числа задач. Но и не переборщить и не гнаться за "универсальным на свете" решением.
Обращение за пределы памяти переменной это достаточно частая ошибка, а конструкции вида stream->buffer[stream->buffer_pos++] провоцируют программиста их совершать. Сейчас нам надо вернуть один байт, а условно "завтра" попросят вернуть int (явно попросят, ну согласитесь, не с int, так с чем-то еще) и у вас уже определенно не будет такой конструкции return stream->buffer[stream->buffer_pos++]; в коде программы. Так может и незачем за такие конструкции цепляться?✅ 2024/07/08 16:23, Gudleifr #42
Но оптимизация это все-таки не про сокращение числа строчек в программе... а про ООП, информационные модели, интересные приемы и архитектурные решения Вся эта мутота про "ООП и прочее" это даже не про оптимизацию, а про обфускацию.ошибка, а конструкции ... провоцируют программиста их совершать Не ошибки, а возможности. Программист должен управлять программой, а не программа — программистом.✅ 2024/07/08 16:33, veector #43
Я модифицировал вашу краткую функцию вот таким образом Извиняюсь, не корректно выразился. Я имел в виду модификацию того блок кода, который вы привели в качестве примера. Саму функцию, её прототип, изначально давал я в своем сообщении. Т.е. это я изначально не предусмотрел default_value , вы лишь написали её содержимое согласно прототипу. И вот на этот момент я как раз и хотел бы обратить внимание, что зачастую важнее проектирование вот этих прототипов и добавление пояснений. Даже в процессе разработки кода очень полезно прислушиваться к идеям и не бояться предлагать изменять то, что было дано как задание.✅ 2024/07/08 16:36, veector #44
Gudleifr, если не секрет, сколько вам примерно лет (мне чуть больше сорока)? Вы давно работаете разработчиком софта на нативных языках?✅ 2024/07/08 16:50, Gudleifr #45
если не секрет Это не секрет, а защита от дурака. Введя мой ник в Google (и легко отсеяв моих малолетних тезок), Вы должны быть вполне способным прояснить этот вопрос, зачёркнуто автором сайта, потому что нарушена универсальная заповедь: не желай другим того, чего себе не желаешь.✅ 2024/07/08 16:56, veector #46
И после этого вы ещё смеете пытаться ограничивать программистов в их возможностях? Нигде не ёкает?✅ 2024/07/29 00:00, alextretyak #47
veectorКод программы — это же не константа, он постепенно изменяется. У меня несколько иное мнение на этот счёт. Бóльшая часть кода — да, скорее всего, рано или поздно будет либо дописана/изменена, либо вообще выкинута и заменена на другой код. Но есть код, который, как я считаю, должен быть именно константой, либо стремиться к этому. Для того, чтобы можно было опираясь на него двигаться дальше. Это как фундамент для здания. Если постоянно переделывать фундамент, то постройка дома так никогда и не завершится.
Взять к примеру недавний случай со сбоем CrowdStrike (https://habr.com/ru/news/831158/). С моей точки зрения, причина произошедшего инцидента не в том, что разработчики ошиблись или что они недостаточно хорошо протестировали обновление драйвера, а в самой необходимости этого обновления. Если в системах, обслуживающих банки и аэропорты (https://habr.com/ru/news/829912/), необходимо постоянно обновлять системные драйверы, то тут явно что-то не так. Драйверы — это не прикладное ПО. К ним вполне применим принцип «работает — не трогай». Для их обновления должна быть веская причина. Единственное исключение — графические драйверы для игрового компьютера, но к компьютерам в банках и аэропортах это не относится.
Поэтому я категорически против идеологии что "код (любой код!) должен постоянно изменяться", т.к. считаю, что это неизбежно будет приводить к катастрофам.
Обсуждаемая функция read_byte() , конечно, не настолько критична, как системный драйвер. Но она является частью ядра стандартной библиотеки языка программирования. Ядра, которое написано с расчётом на то, что на его реализацию, а не просто внешний интерфейс/набор функциональности, можно полагаться при написании приложений. Каждое изменение в ядре стандартной библиотеки я стараюсь отслеживать на регрессию.
Так, оптимизация для чтений файла большими блоками потребовала добавления в код функции чтения единственной условной конструкции, но из-за неё компилятор перестал встраивать эту функцию. Это обнаружилось после того, как я заметил, что чтение маленькими блоками замедлилось на 12% после коммита оптимизации чтения большими блоками. Пришлось добавить проверку времени компиляции, которая "отключает" эту условную конструкцию при чтении маленькими блоками. Вот соответствующий коммит: https://github.com/alextretyak/ file_for_humans/ commit/ a7cdc7ae23107550c89bbd6e9f776702ba93c073Обращение за пределы памяти переменной это достаточно частая ошибка, а конструкции вида stream->buffer[stream->buffer_pos++] провоцируют программиста их совершать. Я считаю, что все обращения к недействительной памяти должны отслеживаться и не допускаться на уровне языка программирования (ну, как минимум должна быть опция, которая включает отслеживание всех таких невалидных обращений, если такое отслеживание отключено по умолчанию из соображений производительности).Сейчас нам надо вернуть один байт, а условно "завтра" попросят вернуть int Нет. Не попросят. Для чтения int уже есть отдельная функция — read_bytes<sizeof(int)>(&i) . Так что имеющийся код реализации read_byte() — это окончательный вариант функции чтения байта из файла. И по задумке этот код должен давать понять, что это очень простая и быстрая функция. И что её можно спокойно вызывать даже для многомегабайтных файлов (другими словами — вызывать сотни миллионов раз без опасения, что это будет замедлять процесс обработки файла, и без желания переписать код на поблочное чтение файла). Разумеется, одного исходного кода не достаточно, и хороший программист должен опираться прежде всего на тесты производительности, но и тут функция read_byte() показывает себя очень неплохо — замедление всего в 1.3 раза при чтении уже находящегося в кэше операционной системы файла с разбивкой по строкам, без какой-либо обработки прочитанных данных. Т.е. фактически, это максимально возможное замедление.
Но, возвращаясь к постфиксному инкременту. Я сейчас пишу статью, посвящённую сравнению моделей итераторов в различных языках программирования. И во многих случаях использование постфиксного инкремента также существенно упрощает код. А именно: в реализациях метода next() , осуществляющего переход к следующему элементу итерируемого объекта, в языках программирования Python, Rust и Java. Конкретно, речь о строке return *b++; в реализации итераторов массива (https://github.com/alextretyak/iterators/blob/master/array.cpp) и о строке return cur++; в реализации итераторов диапазона (https://github.com/alextretyak/iterators/blob/master/range.cpp). Без использования постфиксного инкремента соответствующую строку кода пришлось бы заменить на три строки (с добавлением переменной result), что заметно ухудшит читаемость, т.к. все эти методы состоят всего из пары строк кода.
Но если говорить про типичные функции в типичных программах, то особой необходимости в постфиксных инкрементах/декрементах конечно нет.✅ 2024/07/29 11:35, Gudleifr #48
Я сейчас пишу статью, посвящённую сравнению моделей итераторов в различных языках программирования Лучше напишите "Об общей ошибочности концепции языков, требующих итераторов".✅ 2024/07/29 17:44, DEAD CODE #49
Лучше напишите "Об общей ошибочности концепции языков, требующих итераторов". Не хочется самому это написать? Тема была бы рассмотрена с двух углов, точек зрения. А мы бы получили объективную картину.✅ 2024/07/29 18:20, Gudleifr #50
Не хочется самому это написать? Во-первых, для меня это очевидно. Во-вторых, это должно быть очевидно любому, кто всерьез учился программировать (ну, или хотя бы освоил ликбез у меня на форуме). А, вот, как упражнения для того, чтобы сдвинуть этот форум с мертвой точки, оно бы и полезно.
Хотите разобраться? 1) Каким из двух взаимозаменяющих свойств должна обладать машина, на которой возможен "проход по массиву"? 2) Как соотносятся "итераторы" и теорема о сохранении инварианта в цикле?✅ 2024/08/03 14:53, Автор сайта #51
alextretyakвсе обращения к недействительной памяти должны отслеживаться и не допускаться на уровне языка программирования Согласен, тоже обращал на это внимание.пишу статью, посвящённую сравнению моделей итераторов в различных языках программирования. Если не трудно, дайте ссылку по мере готовности.
Gudleifr, вбил по очереди в поиск оба Ваших вопроса, на первой странице поисковой выдачи нет ссылок на Ваш форум. На эту страницу есть ссылки, а вот к Вам — нет. Для тогочтобы сдвинуть этот форум с мертвой точки надо заняться поисковой оптимизацией. Без неё можно долго находиться в мёртвой точке.✅ 2024/08/03 15:07, Gudleifr #52
вбил по очереди в поиск оба Ваших вопроса Зачем? Это же самые начала.✅ 2024/08/12 15:17, Вежливый Лис #53
компилятор у вас не получится У нас тут был Бурановский Дедушка, который собирался координировать деятельность против тех, кто саботирует конструктивную работу. Я предложил ему сделать специальный отдельный форум для этой активности. Интересно, каков прогресс?✅ 2024/08/12 21:49, Автор сайта #54
Почитайте заголовок статьи и собственные слова. Всё сходится?✅ 2024/08/14 10:31, veector #55
alextretyak, раз уж у нас инкременты спустились на абстрактный программный уровень фундамента, тогда позволю себе пару абзацев про фундамент.Но есть код, который, как я считаю, должен быть именно константой, либо стремиться к этому. Для того, чтобы можно было опираясь на него двигаться дальше. Это как фундамент для здания. Если постоянно переделывать фундамент, то постройка дома так никогда и не завершится. Парадигма ООП, по сути, копирует свойства реального мира на подходы в проектировании софта. Как и в реальном мире, однотипные фундаменты годятся только для однотипных зданий — так и в софте.
Каждый крупный, хороший и успешный программный проект базируется на своем собственном фундаменте, подходящем для данного проекта.
Вокруг не сметное количество «библиотечных» фундаментов, практически одинаковых по функционалу, но написанных по-разному, на разных языках, разных концепциях, а написаны на одном языке, имеют разную реализацию.
Одной только «стандартной Cи библиотеки» несколько общеизвестных вариантов (https://en.wikipedia.org/wiki/C_standard_library#Implementations ). А сколько их в каждом компиляторе (у майков своя, у IAR своя, у Keil своя и т.д.)! Не говоря уже о том, что модулей, подобных вашему, есть не сметное количество, в каждом программном проекте найдется что-то подобное, но своё. Даже вы сделали своё, хотя никакого проекта я по вашей ссылке не заметил.
Нет, я не агитирую отказываться от фундаментов и библиотек, т.к. без них программный проект невозможен. Успех и качество проекта зависит от того, какой фундамент положен в его основе. Я лишь хочу предостеречь от заблуждения — «вот напишу один раз и буду везде использовать».
Фундамент подбирается под проект, а не наоборот. Если подходящего фундамента нет, то нужно сделать новый. Иначе, вы рискуете построить кривое здание на не подходящем ему фундаменте (но зато сэкономили время на фундамент).Если постоянно переделывать фундамент, то постройка дома так никогда и не завершится. Все верно, поэтому, разработчики софта нашли очень изящное решение — интерфейсы, это соглашения, которые устанавливают правила взаимодействия различных частей проектируемой программы (в том числе взаимодействие с фундаментом). Это позволило переделывать фундаменты реже и более грамотно, постепенно добиваясь того самого, качественного фундамента для крупного проекта.
От качества интерфейса, на сколько он будет подходить проекту, и будет зависеть качество и кривизна всего проекта.Взять к примеру недавний случай со сбоем CrowdStrike... Драйверы — это не прикладное ПО. К ним вполне применим принцип «работает — не трогай». Хотел бы немного пояснить за «драйверы», а то у многих наблюдает какое-то заблуждение на счет них. Если прикладной фундамент — только для прикладного софта, то «драйверы» — это фундамент для системного софта.
Более техническим языком — драйвер, это обычная программа, работающая во внутреннем окружении операционной системы и написанная строго для задач операционной системы (в рамках информационной модели, действующей внутри конкретной реализации операционной системы). А то, что эта программа носит отдельный тип/класс «драйвер», так это просто для удобства — сказал «драйвер принтера для винды», и сразу всем программистам понятно, куда смотреть, что в программе должно быть и чего от неё ожидать.
ИМХО, если кто-то считает, что «драйверы» это что-то магическое, одни раз написал и не трогай — то, к сожалению, этот кто-то находится в самом начале целой цепочки заблуждений (еще с фундамента прикладного софта).
Ничего идеального, слава богу, нет. Поэтому, как внутреннее устройство ОС влияет на «драйвер», так и «драйвер» влияет на внутреннее устройство ОС. Рано или поздно, но практически всегда, во внутреннее устройство ОС вносятся изменения (чтобы она не была кривым и косым зданием из-за недостатков в ранее созданном фундаменте).
PS. Не хотел никого обидеть, просто не знаю, как ещё более доходчиво объяснять.✅ 2024/08/14 10:38, veector #56
все обращения к недействительной памяти должны отслеживаться и не допускаться на уровне языка программирования Согласен, тоже обращал на это внимание. Автор сайта, alextretyak, это свойство, о котором вы пишете, возможно только если изменить информационную модель окружения, в рамках которой будет написана и исполняться программа, сделать её отличной от нативной модели, используемой процессором, т.е. создать "фреймворк". Это реализовано, например, в JAVA и C#. И, на сколько понимаю, в Rust.
PS. Только не путайте фреймворк (правила) с виртуальной машиной (средой исполнения).✅ 2024/09/02 00:00, alextretyak #57
Автор сайтаЕсли не трудно, дайте ссылку по мере готовности. Статья опубликована.✅ 2024/09/02 22:24, Автор сайта #58
Спасибо, буду читать, а местами перечитывать. Хорошо, когда кто-то делает за тебя умственную работу :) Есть кое-какие мысли по поводу ещё большего упрощения (хотя как посмотреть на эту простоту, с какой стороны). Но пока не готов изложить.
Кое-что написал в смежной теме. Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|