Недавно один знакомый, которого я знаю через совсем не программистские круги, попросил помочь ему с лабораторной по C++. В коде было примерно следующее:

void do_something(MyObj *input[], int count)
{
    MyObj **copy = new MyObj*[count];
    for (int i = 0; i < count; ++i)
        *copy[i] = *input[i];
    ...
}

Этот случай напомнил о том, что тема указателей является едва ли не основным источником ошибок у изучающих язык студентов, а заодно — своеобразным перевалом, преодоление которого сопряжено с «щелчком» в голове, вызывать который, увы, умеют не все преподаватели. Надеюсь, предложенная ниже лингвистическая аналогия поможет нынешним студентам постигнуть эту концепцию, а их преподавателям или друзьям — донести до них это знание.

Лет десять назад на одном форуме была загадана детская, вроде, загадка:
Для чего еду обеда
Людоедоедоеда
Пригласила на обед
Людоедоедовед?
Я хочу показать, что эта загадка имеет самое прямое отношение к C/C++, поскольку тема указателей легко может быть разобрана по аналогии.

Смотрите. Чтобы понять, что такое «еда обеда людоедоедоеда», нам нужно произвести лингвистический анализ последнего слова. Проще всего это сделать, читая корни справа налево:

  • Людоед = тот, кто ест людей.
  • Людоедоед = тот, кто ест людоедов = тот, кто ест тех, кто ест людей.
  • Людоедоедоед = тот, кто ест людоедоедов = тот, кто ест тех, кто ест людоедов = тот, кто ест тех, кто ест тех, кто ест людей.

По удачному стечению обстоятельств, тот же порядок чтения — справа налево — применяется в C/C++ для ссылочных типов данных:

  • MyClass* = указатель на MyClass.
  • MyClass** = указатель на MyClass* = указатель на указатель на MyClass.
  • MyClass*** = указатель на MyClass** = указатель на указатель на MyClass* = указатель на указатель на указатель на MyClass.

С правой частью разобрались, теперь принимаемся за левую.

  • Обед людоедоедоеда = обед того, кто ест людоедоедов = людоедоед.
  • Еда обеда людоедоедоеда = еда людоедоеда = еда того, кто ест людоедов = людоед.

Стало быть, людоедоедовед (= тот, кто ведает теми, кто ест людоедов) пригласила на обед людоеда. Очевидно, цель такого приглашения — покормить своих подведомственных людоедоедов.

То есть, лингвистическое правило оказывается таковым: поставив слово «еда» или «обед» слева от слова, оканчивающегося корнем «ед», мы скидываем этот последний корень, оставляя только словарную основу до него.

Но аналогично же ведут себя и указатели в C/C++: если слева от сущности, имеющий тип «указатель на T», или «T*», мы поставим символ указателя (ту самую звёздочку), то в результате мы скинем один лишний указатель из типа, получив, собственно, T (или, если быть совсем точным, T&, но темы ссылок и прочих lvalue я сейчас намеренно не хочу касаться). Совсем грубо (игнорируя некоторые правила синтаксиса) можно записать, что

  • *(T*) === T,
  • *(T**) === T*,
  • **(T**) === T,
  • *(T***) === T**,
  • и так далее.

Что в итоге? Я не сформулировал никакого чёткого правила и не сказал про указатели ничего нового. Я лишь надеюсь, что эта лингвистическая аналогия поможет кому-либо из изучающих C/C++ быстрее понять эту тему. Если принять звёздочку за еду, а Люду — за базовый тип… Вы поняли.

Комментарии (163)


  1. jaiprakash
    15.07.2018 23:51

    А ведь есть языки, лишённые радости указателей!


    1. ru_vlad
      16.07.2018 00:15

      А может это и не плохо?
      Java хороший пример. (Да, знаю что можно использовать ссылки)


      1. barker
        16.07.2018 00:36

        Какие такие ссылки?


      1. 0xd34df00d
        16.07.2018 00:43

        Всякие хаскели еще лучше. Ни указателей, ни ссылок.


        1. DASM
          16.07.2018 01:29

          ни софта на них…


          1. 0xd34df00d
            16.07.2018 02:52

            Софта на хаскеле на моей машине поболе, чем на джаве.

            Да и вам как программисту самому писать или радоваться наличию софта?


            1. DASM
              16.07.2018 02:57

              честно говоря не осилил я функциональщину. То ли статей толковых мало, то ли мозгов. Не понял фишки


              1. 0xd34df00d
                16.07.2018 02:59

                Смотря что вы изучали. Менять цикл на map просто так действительно смысла не так уж много.


                1. DASM
                  16.07.2018 03:01

                  Ммм… какой map? std::map пользую, а Хаскель причем тут?


                  1. 0xd34df00d
                    16.07.2018 03:13

                    Ну такой вот map. Хотя лучше более общая штука, конечно.


                    1. 0xd34df00d
                      16.07.2018 20:10

                      Видимо, кому-то очень не нравится функциональщина. Ну ладно.


                      1. Vladislav_Dudnikov
                        16.07.2018 20:20
                        +1

                        Я думаю дело в другом.
                        Представьте, что вы веган, что бы вы говорили людям?


                        1. 0xd34df00d
                          16.07.2018 20:22

                          В треде об особенностях еды можно было бы и обсудить причины, побудившие бы меня быть веганом. Самое оно, как по мне.

                          И посмотрите на самый первый комментарий этой ветки.


                          1. Vladislav_Dudnikov
                            16.07.2018 20:25
                            +1

                            Я просто пошутил.


                            1. 0xd34df00d
                              16.07.2018 20:26

                              У меня всё очень плохо с восприятием шуток, прошу простить.


                              1. DASM
                                16.07.2018 22:52
                                +2

                                Ну на самом деле Вы двигаете функциональные языки, словно это какая-то панацея. Все мои потуги что-то написать напоминают мне попытки забить шуруп молотком. У моих объектов в программах есть состояния, которые надо хранить — и то, что я прочел о ф-ных языках напоминает мне костыли. Мои проги пишутся для реального мира — embedded. И куда там это воткнуть — совершенно не понимаю (даже если были бы компиляторы соответсвующие). Простейшая задачка становится математическим ребусом. Вероятно есть, и может даже много, задач, где ф-ное программирование в тему. Я пока что не вижу таких, и судя по минусам Вам — не я один.


                                1. Druu
                                  16.07.2018 23:07

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

                                  Ну так естественно в этом случае у вас будет ощущение забивания шурупом молотка. Функциональщина нужна не за тем, чтоб описывать состояние, а за тем, чтобы состояния не было.


                                  Мои проги пишутся для реального мира — embedded.

                                  Тогда вам точно функциональщина не нужна. Ее смысл в итоге сводится к декларативности (вместо того, чтобы описывать процесс получения результата, описываем сам результат, а уже компуктер догадается, как его получить). В embedded вам нужно описывать процесс by design.


                                  1. 0xd34df00d
                                    16.07.2018 23:14

                                    Функциональщина нужна не за тем, чтоб описывать состояние, а за тем, чтобы состояния не было.

                                    Неа. Если достаточно упороться монадками, то наступает просветление и понимание, что чистоты нет, стейта нет, эффектов нет, потому что это сами по себе не дискриминирующие термины.

                                    State — это просто удобная абстракция некоего паттерна композиции функций, чтобы создать видимость объективно существующего стейта. Но мы ведь понимаем, что это ерунда.

                                    Программа, производящая IO-экшн, вполне себе чистая. Ну, просто как набор правил, как этот экшн произвести. Точно так же, как, на самом деле, и программа на С. Просто в программе на С у вас весь код — одно большое IO.

                                    Поэтому что важно — возможность себя ограничить. Если вы видите функцию, которая живёт не в IO, то вы точно1 знаете, что файлы она читать-писать не будет и на сервер не постучится. Если вы видите функцию, которая живёт в State s, то вы точно знаете, чем ограничено её «состояние».

                                    Ну я тут, короче, в очередной раз за систему типов и всё такое топлю.

                                    1
                                    Если погрепаете на тему unsafePerformIO или всякие там SafeHaskell включите, но это несущественно в рамках дискуссии.


                                    1. Druu
                                      17.07.2018 00:47

                                      Неа. Если достаточно упороться монадками, то наступает просветление и понимание, что чистоты нет, стейта нет, эффектов нет, потому что это сами по себе не дискриминирующие термины.

                                      Ну так стейта нет — это то, о чем я и говорю. Если же у вас стейт есть, и вы пытаетесь его делать на ФП — то что-то не так. В смысле самого мышления.


                                      Не вижу препятствий в eDSL для этого эмбеддеда (см. вышеупомянутый Ivory), статически тайпчекаемого и верифицируемого, описывающего при этом вполне императивный процесс.

                                      Только DSL и верификация ортогональны функциональному программированию. Вообще, конечно, термин сейчас сильно размыт и следует по-хорошему уточнять, что под фп подразумевается. Я подразумеваю в базе чистую лямбда с надстройками. То есть какой-нибудь Scheme без макросов — сферическое ФП в вакууме.


                                      1. 0xd34df00d
                                        17.07.2018 01:07

                                        Ну так стейта нет — это то, о чем я и говорю. Если же у вас стейт есть, и вы пытаетесь его делать на ФП — то что-то не так. В смысле самого мышления.

                                        Я про то, что его вообще нет в том смысле, что это бессмысленный термин сам по себе.

                                        Вот вы fold или unfoldr делаете, аккумулятор — это стейт?

                                        Я вот на Charts с их монадическим Easy API графички построил, у меня есть стейт?

                                        Или вот я вообще взял произвольную монаду и сижу в do-нотации, выковырял там из неё какой-то x и какой-то y типа
                                        foo :: Monad m => m Int
                                        foo = do
                                            x <- f1
                                            y <- f2
                                            pure $ x + y
                                        

                                        У меня здесь есть стейт, если m ~ Maybe и f1 и f2 соответствующие? А если теперь m ~ State s, и f1 = gets foo, f2 = gets bar?

                                        Только DSL и верификация ортогональны функциональному программированию. Вообще, конечно, термин сейчас сильно размыт и следует по-хорошему уточнять, что под фп подразумевается. Я подразумеваю в базе чистую лямбда с надстройками. То есть какой-нибудь Scheme без макросов — сферическое ФП в вакууме.

                                        Ну, я имею в виду написание eDSL и использование имеющейся в языке системы типов (да, я помню нашу дискуссию на эту тему) для предоставления некоторых гарантий. Хотя какой-нибудь идрис для этих целей подойдёт ещё лучше, потому что гарантий там можно выразить ещё больше, и do-нотация не только для Monad и Applicative.

                                        ФП такого толку — это про типы, ИМХО. Лямбда-исчисление я вам и на плюсах замутить могу. Туплы на них прикольно делаются, кстати, но то такое.


                                        1. Druu
                                          17.07.2018 02:46

                                          Вот вы fold или unfoldr делаете, аккумулятор — это стейт?

                                          Стейт — это не то, что в коде написано, а то, как код воспринимается (от кода при этом может зависит насколько сложно описываемые им вещи рассматривать в рамках той или иной парадигмы). Если вы рассуждаете об аккумуляторе фолда как о стейте — это стейт. Если нет — то нет. Аналогично с монадами. Если вы монадический код в IO воспринимаете как просто императивный код — ну, у вас тут стейт.


                                          ФП такого толку — это про типы, ИМХО. Лямбда-исчисление я вам и на плюсах замутить могу.

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


                                          1. 0xd34df00d
                                            17.07.2018 17:54

                                            Если вы рассуждаете об аккумуляторе фолда как о стейте — это стейт. Если нет — то нет.

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

                                            Потому что именно отсутствие альтернатив вынуждает строить программу и рассуждать о ней определенным способом.

                                            Ну вы и на хаскеле можете всё писать в IO с IORef'ами, никто не запретит.

                                            А если вы как-то так внезапно добавите в C++ такую систему типов, что можно будет гарантировать чистоту, иммутабельность и прочие дорогие сердцу вещи, то да, у вас будет функциональное подмножество.


                                  1. babylon
                                    17.07.2018 01:38

                                    Функциональщина нужна не за тем, чтоб описывать состояние, а за тем, чтобы состояния не было

                                    Функция — это объект внутри которого выполняется некоторый код и куда опционально передаются параметры или возвращается результат. В этом смысле ФП это по прежнему ООП.


                                    1. Druu
                                      17.07.2018 02:48

                                      Одно и то же можно сказать разными способами. То, что можно сказать в ФП, можно сказать и в ООП. И наоборот.


                                      1. babylon
                                        17.07.2018 03:00

                                        Есть ЯП в которых нет объектов. Так что ваш тезис и тут некорректен.


                                        1. Druu
                                          17.07.2018 03:01

                                          Есть ЯП в которых нет объектов.

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


                                          1. babylon
                                            17.07.2018 04:02

                                            Программирование на JSONNET это ООП?


                                            1. Druu
                                              17.07.2018 04:13

                                              Нет, если по парадигмам, то это ближе всего к ФП, наверное. От ООП там вроде и вовсе ничего нету.


                                              1. babylon
                                                17.07.2018 05:17

                                                Верно, но тем не менее там сполошь и рядом объекты. В их построении не хватает сингулярности. Но это уже другая важная тема. Которую все старательно обходят стороной.


                                                1. Druu
                                                  17.07.2018 09:41

                                                  Это вы к чему, с-но?


                                                  1. babylon
                                                    17.07.2018 11:15

                                                    К тому что массивы еще как то в объекты собрать можно, а вот функции нет. На уровне компилятора. И классы кстати тоже:) Lisp в этом ряду стоит обособоленно.


                                                    1. Druu
                                                      17.07.2018 12:34

                                                      К тому что массивы еще как то в объекты собрать можно, а вот функции нет. На уровне компилятора. И классы кстати тоже

                                                      Можно, т.к. язык тьюринг-полный. Только, думаю, кодировка вам откровенно не понравится :)


                                                      1. babylon
                                                        17.07.2018 12:58

                                                        Нет нельзя. Нельзя отделить контекст от параметров.


                                                        1. Druu
                                                          17.07.2018 14:21

                                                          > Нет нельзя.

                                                          Можно.


                                                          1. babylon
                                                            17.07.2018 14:56

                                                            "fun":[1,2] — плс! хочу функцию:)


                                                            1. 0xd34df00d
                                                              17.07.2018 17:55

                                                              А что она должна делать?


                                                            1. Druu
                                                              18.07.2018 00:04

                                                              Чего, не понял?


                                                    1. 0xd34df00d
                                                      17.07.2018 17:56

                                                      Я вот вообще не понял, что означает собирание массивов в объекты, и чем функции хуже массивов.


                                                      1. babylon
                                                        17.07.2018 18:54

                                                        Самый простой вариант.


                                                        [["key1","value1"],["key2","value2"]] последовательный поиск по ключу
                                                        {"key1":"value1","key2":"value2"} - прямой доступ.

                                                        Функция это уже объект.


                                1. 0xd34df00d
                                  16.07.2018 23:08
                                  +1

                                  Неа, это не панацея. Есть области, в которых функциональные языки на текущем уровне развития — исключительно хреновый выбор. Какие-нибудь числодробилки, скажем, или задачи, где нужно просто очень много памяти. Под такие задачи я тоже беру C++ и счастливо пишу на нём.

                                  Но в остальных задачах, от, не знаю, какой-нибудь банальной обработки логов до написания компиляторов и всяких сервисов-микросервисов — да, функциональщина — очень хороший выбор. И с написанием стейтфул-программ там тоже всё вполне хорошо (не знаю, чем вам тот же State — костыль). А с многопоточностью ещё лучше, начиная от принципиального отсутствия класса проблем, связанных с разделяемыми данными, и заканчивая такими примитивами, как STM, например.

                                  Иными словами, мне проще перечислить задачи, где чистое ФП будет плохим выбором, чем где оно будет хорошим. Короче список получится.

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


                                  1. DASM
                                    16.07.2018 23:24

                                    там похоже совсем не то. Под эмбеддед я понимаю микроконтроллеры. Вот пишут «описываем сам результат, а уже компуктер догадается, как его получить).» Совершенно не понимаю. Вот сидит себе микроконтроллер. Ждет пакет инфы с радиоэфира. По получении должен сделать то, потом то, все со строгой времянкой и в зависимости от состоянии кучи датчиков. Как тут описать результат — непонятно. Более менее все ясно с числами Фиббоначи только мне. Да и то, там рекурсия (?) за которую в embedded руки отрывают — мало ресурсов. Даже за динамическое выделение памяти не всегда по голове гладят. По-моему язык пригоден для математиков, задачи реального мира если и можно решить, то выглядит это как несчастная сова на глобусе. Но я не сильно вникал в ФП


                                    1. 0xd34df00d
                                      16.07.2018 23:33

                                      там похоже совсем не то.

                                      Вы про тот же Ivory? Я его не тыкал совсем, так, вместе с вами сейчас документацию читаю.

                                      Вот пишут «описываем сам результат, а уже компуктер догадается, как его получить).»

                                      Это уже пролог какой-то. Я бы сказал, что в функциональщине предполагаемая догадливость компуктера сильно меньше.

                                      Более менее все ясно с числами Фиббоначи только мне. Да и то, там рекурсия (?) за которую в embedded руки отрывают — мало ресурсов.

                                      Не, почему? Ivory вполне может компилировать это вот n `times` f во вполне себе императивный цикл. Собственно, подробное описание этого примера ровно об этом и говорит: «Here’s an Ivory procedure which computes Fibonacci numbers by mutating values on the stack in a loop.» На стеке. В цикле.

                                      Даже за динамическое выделение памяти не всегда по голове гладят.

                                      «Systems Programming: Ivory is well suited for writing programs which interact directly with hardware and do not require dynamic memory allocation.»

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

                                      Для математиков пригодны более другие языки, кстати, в хаскеле есть кое-какие криво сделанные вещи, из-за которых в некоторых областях конкретно он будет тоже очень плохим выбором (ну там, формальное доказательство теорем всякое, всё такое). А про реальные задачи, ну, я уже писал.


                                      1. DASM
                                        17.07.2018 00:37

                                        OK, спасибо за пояснения, думаю мне дергаться на этот счет рано. Вот что реально заставляет нервничать — это С++ со всеми его нововведениями, по-моему они задались целью сделать самый сложный язык на свете. Глядя на некоторые новые исходники на С++ хочется спросить «на каком это языке.» Впрочем отвлекся.


                                        1. 0xd34df00d
                                          17.07.2018 00:46

                                          Ну, у меня есть знакомые товарищи, которые сидят на C++03 и в ус не дуют, им норм, так что это зависит.

                                          Я, впрочем, и плюсы люблю нежной любовью, люблю обмазаться свежими темплейтами и компилировать. Из всех нововведений меня только std::launder раздражает. Но это дело такое, да.


                                    1. Druu
                                      17.07.2018 00:58

                                      Ждет пакет инфы с радиоэфира. По получении должен сделать то, потом то, все со строгой времянкой и в зависимости от состоянии кучи датчиков. Как тут описать результат — непонятно.

                                      Ну видите, "должен сделать" — это уже не ФП. С точки зрения ФП — будет понятие некоего абстрактного действия. Вы можете создать действие, скомбинировать это действие вместе с какими-то другими действиями каким-то способом и получить новое действие. Пакет инфы и показания датчиков — это данные. Вы описываете, действие какого вида должно получится из каких данных, то есть описываете ф-ю (в математическом смысле) perform: Data -> Action, при этом не специфицируете, как эту ф-ю требуется вычислять. Далее вы можете ввести какие-то проверки на тот факт, что построенный на указанных данных Action удовлетворяет определенным условиям, например, у вас есть ф-я time Action -> int и вам надо убедиться, что time(perform(data)) < n. При этом если вы свой Action составляли из других экшонов при помощи некоторых комбинаторов, то у вас могут быть разные утверждения вроде (time(action1) = a, time(action2) = b => time(sequence(action1, action2)) = a + b); Вот если вы так все будете воспринимать — вы пишете на фп, при этом в качестве языка можете хоть сишку использовать.


                                      1. DASM
                                        17.07.2018 01:13

                                        боюсь что просмотр итогового кода (в дизасме) может привести к инфаркту, не?


                                        1. Druu
                                          17.07.2018 02:51

                                          боюсь что просмотр итогового кода (в дизасме) может привести к инфаркту, не?

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


                                        1. 0xd34df00d
                                          17.07.2018 18:02

                                          Кстати, нет, компиляторы-то уже вполне умные.

                                          Парсеры на хаскелевском attoparsec приближаются по скорости к boost.spirit, который вообще один из быстрейших вариантов. Забавные бенчмарки бинарных парсеров я тоже видел, где cereal, что ли, уделывает аналогичный высокопроизводительный сишный парсер. Увы, сейчас не нагуглю.

                                          А когда вчера мне компилятор функцию вида

                                          fraction p list = length' (filter p xs) / length' xs
                                              where length' = fromIntegral . length
                                          

                                          скомпилировал в однопроходный цикл по списку, я совсем не удивился.


                                    1. F376
                                      17.07.2018 01:29

                                      Функциональное Программирование (ФП) можно представить себе как такое написание программы, когда ее выполнение представляет собой как бы вложенные друг за другом «математические функции»: fun1(fun2(fun3(x))) У каждой функции есть совершенно чёткий «вход» и совершенно чёткий «выход», также как у математических (вычислительных) функций: Выход = function(Вход). При этом ни одна функция внутри не обращается к каким-либо переменным/памяти/портам/итд, короче ко всему тому, что представляет собой состояние/память. Подобное состояние, если оно есть (к примеру порт), подается только как «вход». И вдобавок к этому функция должна быть детерминирована (делать одно и то же).
                                      Что это даёт? Подобные функции не зависят ни от чего, кроме как своих входных параметров. И поэтому, если каждый раз перезапускать их с одними и теми же аргументами, мы будем получать на выходе один и тот же результат.
                                      Эмбедщику можно это понять так, что данные словно бы передаются в регистрах, и в регистрах же возвращаются назад, ничего более не меняя.
                                      Это очень удобно и для отладки и для написания стабильных программ — проблемы отладки чаще всего заключаются в том что при работе программы меняется какое-то внутреннее состояние (память, переменные, стек итд) и нет возможности сделать «шаг назад». А при ФП парадигме — достаточно подать на «вход» программы одни и те же предварительно записанные данные, и она выдаст один и тот же результат, пока мы не поправим саму программу.
                                      Парадигма такова, что программа как бы «воздействует» на проходящие через нее данные или их потоки, сама же являя собой «чистый закон».
                                      Что интересно, правильная и рекомендуемая методология *NIX программирования это, в некотором смысле, ФП: мелкие отдельные утилиты, каждая выполняющая свою функцию, принимающие данные на вход, и выдающие детерминированно одно и то же, при том что их можно объединять друг за другом, т.е. выход одной утилиты/команды по-цепочке передавать на вход другой.
                                      Пиксельные шейдеры тоже при определенном приближении являют собой ФП-стиль, вместе с реактивным.
                                      Часть существующих программ и так уже написана приближаясь к функциональному стилю.
                                      Реальное ФП, конечно, подразумевает собой гораздо большее, но кое-какие полезные элементы почерпнуть из ФП, как видим, можно.


                                      1. DASM
                                        17.07.2018 01:54

                                        Спасибо, достаточно понятно объяснили, хотя реальная применимость мне пока туманна. Вот допустим хотим мы смоделировать покупателя. Чтобы он что-то купил ненужное, он должен продать что то ненужное (например свой труд). Продав, он получит деньги (состояние объекта). Причем поставить покупателя в функцию покупки просто так не очень просто (эти деньги могут лежать в банке, на них идет процент итп). То есть я могу примерно это представить, но совершенно не понимаю, как в ФП реализуется что-то асинхронное, завязанное на время, возникающее от случая к случаю… А еще такой вопрос, глядя на машинный код ФП программы, вы сможете отличить его от кода, полученного компиляцией с императивного языка?


                                        1. Druu
                                          17.07.2018 02:55

                                          То есть я могу примерно это представить, но совершенно не понимаю, как в ФП реализуется что-то асинхронное, завязанное на время, возникающее от случая к случаю…

                                          В ФП функции — это тоже данные. С-но, действия, процессы, которые при помощи каких-то примитивов комбинируются в их наборы, ивент лупы с хендлерами и любые подобные вещи — это все данные.


        1. Yuuri
          16.07.2018 14:17

          Как это, а Ptr/ForeignPtr/StablePtr? ;)


          1. 0xd34df00d
            16.07.2018 15:37

            Это скорее хендлы.


            1. 0xd34df00d
              17.07.2018 17:58

              Надо, наверное, сразу было пояснить. Хендлы в том смысле, что для нормального программирования на хаскеле они не нужны совсем, эти Ptr'ы вылезают только при работе с FFI, посему относиться к ним лучше не как к указателям, а как к хендлам, описывающим некоторые внешние относительно языка ресурсы.


      1. Gutt
        16.07.2018 09:54

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


    1. SinsI
      16.07.2018 01:30

      Как такие языки решают проблему модификации «тяжёлых» объектов?
      Вот надо в картинке изменить пару пикселей — не создавать же полную её копию?


      1. 0xd34df00d
        16.07.2018 02:54

        Если вам нужно только изменить пару пикселей и всё, то в любом случае придётся создавать полную копию. Иначе можно удобным полудекларативным, полуимперативным API описать все необходимые изменения и потом один раз их выполнить.

        Примерно так работает, например, Repa, и как-то похоже работает Accelerate.


        1. Druu
          16.07.2018 08:24

          Если вам нужно только изменить пару пикселей и всё, то в любом случае придётся создавать полную копию.

          Создание которой можно соптимизировать в мутацию, если доказать, что на объект существует лишь одна ссылка.


          1. 0xd34df00d
            16.07.2018 18:07

            Что тем проще сделать, чем более строг ваш язык. Особенно если там есть аффинные типы, например.


            1. Druu
              16.07.2018 23:08

              Ну да, я, с-но, и намекал на Clean.


      1. bvdmitri
        16.07.2018 05:59
        +1

        В таких языках любой «объект» на самом деле является указателем на соответсвующий объект. Копию нужно всегда делать явно.


        1. GoldJee
          16.07.2018 08:15

          Вот иллююстрация на Java:

          int i = 1;
          int j = 1;
          System.out.println(i == j); // true
          


          String i = "1";
          String j = "1";
          System.out.println(i == j); // false
          System.out.println(i.equals(j)); // true
          


          1. TheKnight
            16.07.2018 09:58
            +1

            И ни фига подобного.
            В зависимости от того, как сложатся звезды у вас первое сравнение в примере со String может выдать true.
            А может и false.
            ideone.com/BkxRUq


            1. Artyomcool
              16.07.2018 12:03

              В данном конкретном примере звезды должны бы всегда складываться в true, если пользоваться стандартным javac, а не компилить в байткод самому.


              1. TheKnight
                16.07.2018 14:43

                Спасибо за уточнение, попробую перечитать этот момент в спеке.



          1. Akon32
            16.07.2018 10:23

            Дело в том, что компилятор одинаковые строковые константы в пределах класса может записывать в class-файл как одну константу (и делает это в данном частном случае). По ссылке из комментария выше это и происходит.


    1. vics001
      16.07.2018 02:00

      В Java нет указателей, но при работи с большими массивами int и индексами по ним, задача вполне логически тожественная указателям первого уровня.


    1. Yuuri
      16.07.2018 14:19

      Не так страшны указатели, как их арифметика.


    1. amarao
      16.07.2018 18:03
      +2

      До тех пор, пока у процессора есть инструкция «прочитать в регистр по ссылке», любой язык программирования, игнорирующий этот факт, будет просасывать Си по производительности.

      Правильный подход: богатая система типов, запрещающая творить фигню (Rust!), но сохраняющая указатели в полном объёме.


      1. SinsI
        17.07.2018 01:12
        +1

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


        1. evocatus
          17.07.2018 17:33

          менять применяемый алгоритм по мере накопления данных прямо во время выполнения

          Чем JVM уже некоторое время успешно занимается.


  1. harlong
    16.07.2018 00:11

    А если разобрать несколько иначе (людоедо_едо_ед = тот, кто ест еду людоеда = собственно, людоед), то его обед это человек, а еда обеда — обычная человеческая еда. Которую ученая, изучающая людей, пригласила с очевидной целью — съесть. )


    1. mk2
      16.07.2018 00:21

      В таком случае вы промежуточно имеете разбиение людоедо_едоед, а слово «едоед» в русском языке отсутствует.


      1. harlong
        16.07.2018 00:24

        Формально да. При первом прочтении стишка у меня оно в голове само построилось по аналогии с «мясоед» и подобными.


    1. netch80
      16.07.2018 15:36
      +2

      Вполне может быть, что людоед — лев, людоедоед — блоха, людоедоедоед — лягушка.
      Тогда людоедоедоед != людоеду.
      Ну а энтомолог Иванова пригласила льва, чтобы посчитать блох ;)


      1. mk2
        16.07.2018 20:23

        Блоха львов всё же не ест. Так, надкусывает.


      1. funca
        17.07.2018 00:14

        «Любую проблему можно решить путём введения дополнительного уровня абстракции, кроме проблемы слишком большого количества уровней абстракции»


  1. picul
    16.07.2018 01:48

    А в чем парадокс?


  1. Gaernebjorn
    16.07.2018 03:03

    людоедоедоеда», нам нужно произвести лингвистический анализ последнего слова. Проще всего это сделать, читая корни справа налево:

    Людоед = тот, кто ест людей.
    Людоедоед = тот, кто ест людоедов = тот, кто ест тех, кто ест людей.

    Простите, но сразу стало непонятно. Самый правый корень "ед". Может слева направо?


    1. Furax Автор
      16.07.2018 06:02
      -1

      Как раз справа налево. Правый корень «ед» заменяем на фразу «тот, кто ест», словарная основа остаётся. Также как «T*» читается как «указатель на T», то есть, справа налево.


      1. Gaernebjorn
        16.07.2018 10:32

        Вы сейчас пытаетесь объяснить рекурсивно, через указатели :). Я же говорю о логике объяснения в статье.
        Если мы разбиваем слово людоедоедоед, тогда первая строчка (по Вашему описанию) должна быть


        1. людоедоедоед = людоедоедо (лево) + ед (право)
          То есть у вас словесный алгоритм не соответствует написанному коду. А для вот таких вот объяснений-аналогий это как раз самое важное.


        1. Chamie
          16.07.2018 12:25

          Вы имеете в виду, что строки в другом порядке должны быть? Так эти строки — не пошаговое выполнение метода, а просто иллюстрация его на всё более сложных примерах.


    1. Gutt
      16.07.2018 09:57

      Забудьте. Гораздо проще разобрать эту загадку, используя указатели из C, чем наоборот.


  1. Zuy
    16.07.2018 05:12
    +1

    А что, обьяснения, что указатель — это адрес в памяти, где лежит объект не достаточно? Подход в статье ну совсем какой-то мутный.


    1. Furax Автор
      16.07.2018 06:03

      Кому-то достаточно. Лингвистическая аналогия — исключительно для тех, кто не понял с первого раза.


      1. Antervis
        16.07.2018 06:49

        подавляющее большинство с первого раза не поймет ни сухое объяснение через адреса/номера ячеек, ни тем более вашу эм… аналогию. ~90% — визуалы, и объяснять в большинстве случаев надо через рисунок; остальные же редко идут в точные науки, требующие пространственного мышления.


      1. Zuy
        16.07.2018 09:00
        +1

        А если непонимающему просто дать задачу, где указатели реально нужны и он сам придет к их необходимости.
        Мне кажется, нет смысла объяснять указатели дальше одного уровня.
        Вещи типа TObject* objects[] должны как-то сами приходить.


        1. Furax Автор
          16.07.2018 14:17

          Я и говорю: нужен щелчок.


    1. robert_ayrapetyan
      16.07.2018 06:03

      Нет конечно, отсюда и все эти бесконечные статьи. Указатель — это переменная, содержащая адрес, а не сам адрес. Упрощая, вы вводите новичка в заблуждение. Отсюда всякие непонятки с арифметикой над указателями и пр. ((int*)55555+1 = 55563 на 64-х битных платформах). Лучше сказать так: указатель — это некая абстракция над местоположением некоторой сущности в памяти (зависит от реализации, это может быть вообще какой-то дескриптор на экзотических архитектурах, а на интелах — это смещение внутри сегмента, даже здесь не назовешь это просто адресом).


      1. khim
        16.07.2018 06:40

        На самом деле как ни говори, а проблема в том, что указатель — это вещь, которая объединяет несколько разных сущностей, ни об одной из которых новочёк понятия не имеет. Вот все эти «адреса», «память» и прочее — для него «тёмный лес и куча дров».

        Лучшее, что я видел — это «отодвинуть» изучение указателей за ту точку, когда человек уже знает для чего они нужны.

        То есть вначале мы изучаем массивы, структуры, дальше изучаем алгоритмы (деревья, очереди с приоритетами). А потом, когда человек уже привык оперировать с индексами, которые указывают на ячейки, в которых тоже лежат индексы — объяснить ему, что указатель — это такой «синтаксический сахар» для большого массива с названием «опереативная память» — дело пяти минут.

        И всё. И нет никакой магии и никакого «щелчка» в голове, всё буднично и банально.


        1. Antervis
          16.07.2018 06:50

          подскажите, как сделать дерево без указателей?


          1. Rsa97
            16.07.2018 07:10

            Пр изучении программирования деревья (да и списки, стеки, деки, очереди) обычно вводят на базе массива. Заодно показывается принцип сборки мусора.


            1. khim
              16.07.2018 07:42

              Это не «обычно». Это «как надо». Потому что «обычно» стремяться перескочить через все «скучные» вещи и научить какие-нибудь картинки рисовать. Или web-страничку генерировать.

              Почему-то никого не удручает тот факт, что для того, чтобы научиться рассказы писать нужно потратить не один год, рисуя палочки и изучая как подлежащее со сказуемым согласуется.

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


              1. roscomtheend
                16.07.2018 17:44
                +1

                Да даже через русский язык стремятся перескочить, благо там «собирается» даже с ошибками.


          1. yurrig
            16.07.2018 07:19

            Бинарное дерево без указателей, на массиве длиной в 2^n, описано у Кнута, ЕМНИП. Там примерно так: в массиве лежит по индексу 1 корневой элемент, потом оба его дочерние, за ними — 4 их дочерних, и т.д. Нулевой и отсутствующие элементы помечаются как пустые. Для элемента по индексу idx: переход к родителю — idx/2, к левому/правому потомку — 2*idx и 2*idx+1. В каких-то (редких?) случаях так даже может быть экономнее и быстрее, чем на указателях. Disclaimer: я сам всегда на указателях деревья строю, но из песни слова не выкинешь)


            1. khim
              16.07.2018 07:35

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


              1. yurrig
                16.07.2018 08:13

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


                1. khim
                  16.07.2018 09:21
                  +1

                  за счет высокой локальности — промахов процессорного кэша будет поменьше, чем для разбросанного в памяти дерева на указателях.
                  Вот только никакой локальности в этом представлении нет и в помине, так что реально выигрыш происходит только за счёт экономии памяти.


                  1. yurrig
                    16.07.2018 09:48

                    Поясните?


                    1. khim
                      16.07.2018 09:53
                      +1

                      При обработке деревьев, как правило, нужно переходить к левому и правому потомкам, иногда наверх. И крайне редко — к «соседу» слева или справа. А при хранении дерева как вы описали — близко расположены именно «соседи», а полезные ячейки (все три) — очень и очень далеко. На таком дереве разве что «поиск в ширину» хорошо организовывается.


                      1. yurrig
                        16.07.2018 10:10

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


                        1. khim
                          16.07.2018 12:40

                          Это интересное замечание, но, боюсь, неиспользуемые ноды засорят кеш. Хотя, да, было бы неплохо провести исследование. C AVL деревьями даже, может, какой-то выигрыш будет. С красно-чёрными — вряд ли…


                          1. Druu
                            16.07.2018 12:52

                            Если надо, то можно сделать под дерево кастомный аллокатор и обеспечить себе любую требуемую локальность, вообще говоря. Так что особо не важно что там и как сорит.


                            1. khim
                              16.07.2018 13:52

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


                              1. Druu
                                16.07.2018 14:37

                                Не обязательно большим куском, можно кусками поменьше. Сборщики мусора же как-то работают.


                                1. khim
                                  16.07.2018 15:42

                                  Сборщики мусора работают за счёт дополнительной информации. А если вы вы всё-таки заглянули в начало дискуссии, вместо того, чтобы бредить, то обнаружили бы, что обсуждается хранение дерева, когда ссылки left и right отсутствуют, являются виртуальными…


                                  1. Druu
                                    16.07.2018 23:38

                                    Вы уже сами запутались. Давайте по порядку:


                                    1. Если дерево сбалансировано, то вы можете выделить элементы любом наперед заданном порядке и расположить их последовательно, обеспечив локальность для любого наперед заданного метода обхода. Не важно со ссылками или без.
                                    2. Если дерево несбалансировано, то вы так же можете выделить ноды в любом наперед заданном порядке и расположить их последовательно, обеспечивая локальность, но уже используя указатели, чтобы компенсировать "дырки". Толку от неиспользования указателей в этом случае нет, т.к вам "дырки" сожрут всю сэкономиленную память.


                                    1. khim
                                      17.07.2018 00:40

                                      Собственно вы сейчас повторили ровно то, что я написал. И далее зачем-то предложили в конкретную версию варианта 1 (которому вся эта ветка посвящена) вкручивать аллокатор. Куда его там вкручивать и чем он там поможет — мне лично неясно.


          1. khim
            16.07.2018 07:30
            -1

            Очень просто. Заводите массив размером с максимальное количество элементов в этом дереве. Вместо указателей — индексы.

            На самом деле проще всего начать с такой структуры данных, как «ассоциативный массив» на базе хеш-функции.

            Вначале — простой массив пар «ключ/значение», в случае если случаются коллизии… у нас беда.

            Потом — начинаем записывать в соседние ячейки… и на какое-то время можно на этом продержаться.

            Потом — учимся обходить занятые места и устраивать «дыры», если нам это нужно (тут у нас уже получаются вначале односвязные, а потом и двусвязные списки).

            Ну а после этого — можно уже и деревья устроить.


      1. Ryppka
        16.07.2018 08:33

        Вы уверены, что 55555 и 55563 — допустимые значения для адреса целого в ILP64 системах?! ;)


        1. khim
          16.07.2018 09:22

          На большинстве современных процессоров — да. Но на Alpha, почившей в бозе — нет.


          1. Ryppka
            16.07.2018 10:25

            SPARK64? POWER*?


            1. khim
              16.07.2018 12:28

              ARM, POWER — в том же месте, что и x86: теоретически можно самому себе строить проблемы и запретить доступ по невыровненным адресам, но пракстически — этот режим никто не использует. Про SPARC не знаю.


      1. Zuy
        16.07.2018 08:38

        Хммм, вы меня немного запутали. А кто эти люди кому пытаются обьяснить указатели и они не понимают? Они вообще понимают, как работает процессор?
        Получается, что есть какой-то абстрактый человек, который понимает сегменты и смещения в них процессоров Intel, он знает про разницу 32-х и 64-х битной адресации, а когда ему сказали, что '&' берет адрес переменной, а '*' возвращает значение по адресу, он такой:«Нет, вы знаете, не догнал». Странно это как-то.
        Интересно, а эти самые люди ссылки в С++ понимают?


        1. robert_ayrapetyan
          16.07.2018 08:46

          Ссылки это вообще абстракция над абстракцией, их никто не понимает ). А вообще я не согласен лишь с тем, что сишный указатель — это адрес в памяти. Это довольно избитая и обмусоленная тема, вот тут почитайте.


          1. Zuy
            16.07.2018 09:23

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


          1. khim
            16.07.2018 09:36

            Это довольно избитая и обмусоленная тема, вот тут почитайте.
            А что я там должен увидеть? Что люди, не знающие о том, как устроен современный C пишут о нём всякие домыслы? Беглый поиск показал, что никто из спорщих о вкусе устриц с теми, кто их ел, не подозревает о том, что с C99 (а стало быть и с C11) есть такая штука, как intptr_t, которая «закрывает тему»: да, теоритечески указатели в C (и C++) могут быть много чем (intptr_t, строго говоря, опционален), но практически — вы можете просто игнорировать подобные компиляторы.


            1. robert_ayrapetyan
              16.07.2018 18:46

              Осталось определиться, какую тему закрывает intptr_t, и отсылок к стандарту в той теме как раз предостаточно. Предмет текущего спора, напомню, можно ли утверждать, что указатель (pointer в оригинальном стандарте) — это адрес. А не какие типы данных появились в каком стандарте.
              Если как раз почитать стандарты, на которые вы ссылаетесь, то однозначный ответ — нет. Да, численно указатель равен смещению в сегменте данных, но на этом все сходство с offset ptr заканчивается (кстати, даже в оригинальных ассемблерах не употребляют слово адрес, а именно offset — смещение).

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


              1. khim
                16.07.2018 19:19
                +1

                Осталось определиться, какую тему закрывает intptr_t, и отсылок к стандарту в той теме как раз предостаточно.
                Вопрос «является ли указатель числом».

                The following type designates a signed integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
                    intptr_t
                The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
                    intptr_t
                Если мы можем сконевртировать указатель в число, проделать с этим числом всем операции, какие можно производить с числом (в файл, например, записать, или по сетке на другой компьютер послать), то всё, тема закрыта: этот указатель — таки и есть число.

                Предмет текущего спора, напомню, можно ли утверждать, что указатель (pointer в оригинальном стандарте) — это адрес.
                А что такое адрес, извините?

                Да, численно указатель равен смещению в сегменте данных, но на этом все сходство с offset ptr заканчивается (кстати, даже в оригинальных ассемблерах не употребляют слово адрес, а именно offset — смещение).
                Если вы таки про 8086, то там ещё и сегмент был. И указателей было… много разных: near, far, hure

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

                P.S. Собственно на это наткнулись эльбрусовцы, когда завели теги в памяти, чтобы указатели перестали быть просто числами и стали чем-то что OS выделяет и на корректность чего, соотвественно, можно положиться. Вот в тот момент, когда выяснилось, что указатель можно превратить в число и потом — обратно в указатель… вся схема и «провисла:»…


                1. robert_ayrapetyan
                  16.07.2018 20:26

                  Ну про число никто не спорит, там вообще все имеет численное представление, даже буквы.


                  1. khim
                    16.07.2018 20:39

                    Буквы как раз во всех известных мне языках можно в число и обратно преобразовать. А вот указатели — не во всех. В ISO Pascal или в Ada — нельзя. А потому что «безопасность», а потому что iAPX 432, а потому что указатель — это не просто адрес.

                    А вот в C (по крайней мере в современном C) — это таки «просто число», то есть «просто адрес».


                    1. Ryppka
                      16.07.2018 21:42

                      Может быть, в C и число, и указатель — это просто битовые паттерны с разными правилами интерпретации компилятором: старший бит — знак и т.д. Или 4 бита — номер банка и т.д. Нет?


                      1. khim
                        16.07.2018 22:37

                        Дык об этом и речь! В C (по крайней мере при наличии в нём intptr_t) указатель — это просто набор бит, битовый паттерн. Его можно превратить в число и куда-нибудь «послать»… а ведь не на всех машинах это возможно. Забудьте про интерпретаторы, почивший в бозе iAPX32 и даже Эльбрус (хоть вроде как последний таки жив).

                        Вспомните про .NET! Там указатель — это не просто себе последовательность битов. За ним учёт нужен. Превратить его в число, а потом обратно — низзя. Не положено. А в C/C++ — как раз положено! И всё — в .NET встроить C/C++ нельзя. Недаром Micrososft спецподелие изобрёл. Не потому, что ему больше нечем заняться…


        1. Furax Автор
          16.07.2018 08:56

          Это люди, которым в учебнике написали: вот так объявляется массив объектов, вот так — массив указателей на объекты. Вот такой синтаксис обращается к методу объекта по его указателю. Вот так работает оператор new []. Теперь напишите программу, которая будет производить масштабирование сложной геометрической фигуры на плоскости.

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


        1. khim
          16.07.2018 09:27

          Получается, что есть какой-то абстрактый человек, который понимает сегменты и смещения в них процессоров Intel, он знает про разницу 32-х и 64-х битной адресации, а когда ему сказали, что '&' берет адрес переменной, а '*' возвращает значение по адресу, он такой:«Нет, вы знаете, не догнал».
          Нет такого астрактного человека. Есть человек, которому написали как получить данные из формочки и «нарисовать» на основе этих данных HTML.

          Всё равно как если бы математику учили не со сложения и таблицы умножения, а с задач по составлению диффуров и нахождения экстремумов. Понятно, что это полезнее, но без основ — оно всё у вас «провиснет в воздухе» и вы будете делать совершенно идиотские ошибки на ровном месте.


          1. Zuy
            16.07.2018 09:36
            -1

            А чего же он эти данные с формочки парсит на С/C++ не понимая указатели а не на каком-нибудь Python или, прости господи, PHP?


            1. khim
              16.07.2018 09:49

              Проблема не в том, что он парсит формочки на C/C++. Проблема в том, что он считает себя программистом, которому чуть-то нужно подучить C/C++ и всё. Хотя реально — он занимается комбинаторным программированием, дёргая примеры со StackOverflow и комбинирует их случайным образом пока результат не будет походить на то, что требуется.

              А потом вот с этим багажом — хотят изучить C/C++. Причём в том же стиле. А он, зараза, понимания требует. Комбинаторным программированием в нём даже программу в 1000 строк не сделать. Вот беда-огорчение…


              1. Druu
                16.07.2018 10:35

                Да ладно вам, перекладывание байтиков по когнитивной нагрузке от дерганья формочек ничем существенно не отличается. И на стековерфлоу точно так же есть куча готового кода для разных кейсов.


                1. khim
                  16.07.2018 12:30

                  Да ладно вам, перекладывание байтиков по когнитивной нагрузке от дерганья формочек ничем существенно не отличается.
                  Отличиается. Тем, что если вы «не так» переложите формочки — то вы это сразу увидите или, в худшем случае, вам прилетит понятный exception.

                  Если вы «не так» переложите байтики — вам никто худого слова не скажет, просто программа будет вываливаться с невразумительными дампами (да, я знаю, есть много способов во всём этот разобраться — но для этого всё равно нужно какое-то понимание того, что вы делаете).


                  1. Druu
                    16.07.2018 12:36

                    Отличиается. Тем, что если вы «не так» переложите формочки — то вы это сразу увидите или, в худшем случае, вам прилетит понятный exception.

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


                    1. khim
                      16.07.2018 19:26
                      +1

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

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


                      1. Druu
                        16.07.2018 23:49

                        Достаточно понять как меняется программа «методом малых шевелений».

                        exception от этого понятным не станет


                        Те, кто могут писать нетривиальную логику — уже и в указателях могут разобраться.

                        Так я об этом и сказал с самого начала. Перекладывание формочек от перекладывания байтиков ничем не отличается. И там и там — перекладывание. Сложность возникает тогда, когда процесс перекладывания становится нетривиальным, и не важно, формочки это или байтики. Если же процесс тривиален — то, опять же, разницы нет, формочки у вас или байтики, в обоих случаях можно будет справиться "малыми шевелениями" и прочим стековерфлоу-программированием.


                        Здесь справедливости ради стоит заметить, что "глубина абстракции" в случае формочек несравнимо выше. То есть — вы можете, в принципе, досконально хорошо понимать, как работает ваш алгоритм, перекладывающий байтики, но не можете досконально хорошо понимать, как работает алгоритм, перекладывающий формочки. Так что потенциальная когнитивная сложность задач с формочками выше. Но только потенциальная.


                        1. khim
                          17.07.2018 00:45

                          Достаточно понять как меняется программа «методом малых шевелений».
                          exception от этого понятным не станет
                          И? Задачу после некоторого количества случаных измнений вы решите, эксепшн изгоните — значит вы уже программист. Всё как у классика.

                          А вы сюда с каким-то «пониманием» лезете.

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

                          Количество переходит в качество и комбинаторное программирование перестаёт работать…


                          1. Druu
                            17.07.2018 01:02

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

                            Мне кажется, то, что в байтоперекладывании за "сложность" платят меньше — факт ортогональный обсуждаемым вопросам.


                            1. khim
                              17.07.2018 23:31

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

                              Она просто развалится под собственной тяжестью. И этот размер — особо от языка не зависит.

                              Но в случае с web-программированием программы подобного размера можно продавать, а в случае с C++ — нет.

                              А это кардинально меняет всю динамику.


                              1. Druu
                                18.07.2018 00:08

                                Ну так еще раз, дело не в том, что байтоперекладывание сложнее, а в том, что за простое байтоперекладывание не платят, в отличии от. Надо все же верно расставлять акценты.


    1. RadicalDreamer
      16.07.2018 09:31
      +1

      Подход в статье ну совсем какой-то мутный.

      Мне эти «людоедоедоеды» вообще напомнили цитатку с башорга:
      Заголовок спойлера
      «Те, кто водит хороводы — хороводоводы. Те, кто изучают творчество хороводоводов — хороводоводоведы. Те, кто любит читать хороводоводоведов, — хороводоводоведофилы. Те, кто ненавидит хороводоводоведофилов, — хороводоводоведофилофобы. Те, кто поедает хороводоводоведофилофобов, — хороводоводоведофилофобофаги. Те, кто ведет борьбу с хороводоводоведофилофобофагами, — антихороводоводоведофилофобофаги. Те, кто выдает себя за антихороводоводоведофилофобофагов, — квазиантихороводоводоведофилофобофаги!!!»


    1. Goldseeker
      16.07.2018 10:44

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


  1. Duny
    16.07.2018 05:52

    void do_something(MyObj *input[], int count)
    {
        MyObj **copy = new MyObj*[count];
        for (int i = 0; i < count; ++i)
            *copy[i] = *input[i];
        ...
    }
    

    В коде ошибка. copy — это массив указателей.
    В выражении:
    *copy[i] = *input[i];

    происходит разъименование указателя copy[i], который никуда не указывает. Это UB.


    1. Furax Автор
      16.07.2018 06:03

      Об этом и речь.


  1. daiver19
    16.07.2018 06:01

    Это все, конечно, занятно понимать just for fun, но на С++ так писать не надо (в принципе, в 99 процентах случаев больше одного указателя не надо).


    1. BalinTomsk
      16.07.2018 21:25
      +1

      потому что вы на GPU не программировали — я там это добра даже поболе.
      Еще с потоками, с другим принципом обхода массивив и т.д.


      1. daiver19
        16.07.2018 22:43
        +1

        CUDA C — не С++. В статье речь идет о лабе по С++.


        1. Akon32
          17.07.2018 11:23

          CUDA вполне себе поддерживает C++. Как минимум лет 7. Может, не полностью, но в документации сейчас язык уже называется "CUDA C++" или "CUDA C/C++".


  1. COCATb
    16.07.2018 06:04
    +1

    Я просто оставлю это здесь: cdecl.org


  1. TimeCoder
    16.07.2018 07:02

    Другой неплохой путь понимания указателей — это посмотреть на их реальный смысл, память и машинную арифметику. Практика! Структуры данных. Вот прямоугольник, там лежит число — это int. Т.е. область памяти, коробочка такая, нули и единицы которой сами по себе мы интерпретируем как число. Теперь нам надо несколько таких чисел сохранить, прямоугольники в памяти идут один за другим — массив. И ещё одна дополнительная переменная, тоже блок памяти маленький, где записан адрес первого элемента массива. Теперь представим, что таких массивов — несколько. Например, их пять. Они разной длины, кстати. Адреса всех не сохранишь в одной переменной. Создаём массив длиной пять, где будут лежать адреса этих массивов. Какой тип элемента в каждом из пяти массивов? Int. Какой тип каждого элемента в массиве верхнего уровня, хранящего адреса тех пяти массивов? Int*. А для работы с этим самым верхним массивом адрес его первого элемента мы запишем в переменную какого типа? Правильно, int**. Если ещё все это на бумажке нарисовать, то будет ещё понятнее, и главное — не надо ломать мозг лингвистическими закорючками, вы видите практическое применение.
    P.s. я понимаю, что в it много самоучек. В вузе на курсе С нас год муштровали машинной арифметикой, СДиА, битовыми операциями и пр. Считаю, это надо понимать, а если вы пишите на языке, где есть указатели — то тем более. И если в вузе не отложилось, надо наверстать.


    1. jmdorian
      16.07.2018 09:11
      +2

      Да, мне в свое время также объясняли, но к пониманию это не приблизило. Посему я решил для себя что пользоваться ими не буду, от греха подальше. Благо и не пришлось потом.
      А понимание пришло откуда не ждали — когда в качестве хобби решил заняться программированием под AVR и для этого освоил ассемблер. Все сразу стало куда понятнее. Я не утверждаю что студентам нужно в обязательном порядке вдалбливать ассемблер, но как лирическое отступление при объяснении указателей можно было бы.


    1. Akon32
      16.07.2018 10:33

      Примерно так нам и объясняли в вузе. И не припомню проблем с пониманием.


      Имхо, объяснять указатели на каких-то мутных лингвистических аналогиях, а не на предельно ясном устройстве абстрактного процессора — очень странная идея.


    1. stychos
      17.07.2018 02:10

      Вот кстати интересный факт. Мне и самому намного проще представить указатели как данные в памяти. Но объяснить это посторонним в таком виде зачастую просто невозможно, потому мнение автора может иметь смысл.


  1. Sdima1357
    16.07.2018 10:35

    Указатели — зло. Деньги — зло.
    Но зла не хватает. Все языки используют указатели, но не все в этом признаются.


  1. WinPooh73
    16.07.2018 11:55

    И это ещё тема ссылок на указатели и указателей на ссылки не раскрыта.
    С ними было бы что-то вроде:

    Не проказничали чтобы
    Гомофобофилофобы,
    Скоро всех возьмут на вилы
    Гомофилофобофилы.


  1. x-foby
    16.07.2018 14:18

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

    Но за попытку спасибо)


    1. Free_ze
      16.07.2018 18:33
      +1

      На практике не так страшны сами указатели, как синтаксис их объявления.


      1. x-foby
        16.07.2018 19:09
        +1

        Здесь нужно понимать несколько моментов.

        С одной стороны, да, всё всегда упирается в синтаксис, поскольку работаем мы [программисты] с кодом; понятие «переменная» и «указатель» справедливы только для кода, непосредственно железо в этих понятиях не нуждается и ими не оперирует.
        Поэтому формально я с вами согласен, да.

        С другой же стороны, если посмотреть на ситуацию комплексно, то мы сможем свести все причины проблем с указателями всего-то к двум:

        1. Непонимание сути указателей;
        2. Незнание синтаксиса.

        Так вот суть в том, что решить первую проблему лингвистическими аналогиями — невозможно. Здесь нужно просто объяснять человеку, что есть память, что есть переменная. Когда если человек разберётся с этим, тогда вопрос сложности восприятия синтаксиса стоять не будет, так как синтаксис не нужно понимать, синтаксис нужно просто выучить. Вне зависимости от языка (я сейчас даже не о технических, не об ЯП — обо всех: русский, английский, etc.) синтаксис — это аспект, который не требует понимания, он требует только зубрёжки.
        Но здесь мы сталкиваемся со второй проблемой: вы можете зубрить что угодно и сколько угодно, но пока вы не поймёте сути темы — толку ноль.

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

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


      1. Druu
        17.07.2018 00:02

        Это, кстати, да, тому, кто придумал одним и тем же символом (*) обозначать и операцию разыменовывания и спецификатор типа (и вдобавок тот факт, что спецификатор типа принадлежит переменной, а не типу, что откровенная шизофрения), нужно обеспечить пожизненный цик с гвоздями. Да и взятие адреса с побитовым "и" недалеко ушло.


  1. gospodinputin
    16.07.2018 14:18

    Я не сформулировал никакого чёткого правила и не сказал про указатели ничего нового.
    Спасибо за статью, очень познавательно и очень интересно.


  1. technic93
    16.07.2018 15:05

    Если надо дело доходит до ** или не дай бог Страуструп *** гораздо читабельней сделать


    typedef Object* ObjectPtr

    Особенно удобно если потом это всё еще и в массив кладется, и не надо думать что будет когда операторы [] и * стоят в одном выражении.


    1. technic93
      16.07.2018 15:10

      А вообще спасибо, весёлая аналогия получилась :)
      Ещё можно про const упомянуть:


      const char* x;
      // vs
      char* const x;

      тоже справа налево раскрывается.


    1. Inine
      17.07.2018 15:48
      +1

      По инерции начал было соображать, как раскрутить Страуструп***


  1. Nick_Shl
    16.07.2018 16:19

    Что бы понять что такое указатели, нужно сначала получить ASM и его разные аресации. Знание, что "нужно положить адрес этой строчки в DX перед вызовом INT 21h" очень помогает. Во всяком случае помогло мне — когда вся группа в универе сидела и втыкала что же такое указатели, мне все были понятно с полуслова. Что интересно, курс ассемблера x86 у нас тоже был, но позже курса Си…


    1. MacIn
      16.07.2018 19:42

      Это помогает только для самых примитивных случаев.
      «Настоящий» же ад указателей — это когда надо раскручивать по спирали, а не просто справа налево.


  1. ne_zabudka
    17.07.2018 16:57

    Жалко Люду. Максимально сократим её возможное присутствие в пищевой цепочке:
    людоведаедовед
    При условии, что людоведа зовут например Вася, а его жену не Люда.