Указатели C как лингвистический парадокс +47


Недавно один знакомый, которого я знаю через совсем не программистские круги, попросил помочь ему с лабораторной по 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
    /#18884979

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

    • ru_vlad
      /#18885007

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

      • 0xd34df00d
        /#18885055

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

        • DASM
          /#18885099

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

          • 0xd34df00d
            /#18885179

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

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

            • DASM
              /#18885183

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

              • 0xd34df00d
                /#18885189

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

                • DASM
                  /#18885193

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

                  • 0xd34df00d
                    /#18885207

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

                    • 0xd34df00d
                      /#18887805

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

                      • Vladislav_Dudnikov
                        /#18887839 / +1

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

                        • 0xd34df00d
                          /#18887843

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

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

                          • Vladislav_Dudnikov
                            /#18887849 / +1

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

                            • 0xd34df00d
                              /#18887853

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

                              • DASM
                                /#18888145 / +2

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

                                • Druu
                                  /#18888167

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

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


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

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

                                  • 0xd34df00d
                                    /#18888177

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

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

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

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

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

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

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

                                    • Druu
                                      /#18888325

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

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


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

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

                                      • 0xd34df00d
                                        /#18888347

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

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

                                        Вот вы 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.

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

                                        • Druu
                                          /#18888411

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

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


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

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

                                          • 0xd34df00d
                                            /#18891065

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

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

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

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

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

                                  • babylon
                                    /#18888377

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

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

                                    • Druu
                                      /#18888413

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

                                      • babylon
                                        /#18888423

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

                                        • Druu
                                          /#18888425

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

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

                                          • babylon
                                            /#18888463

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

                                            • Druu
                                              /#18888471

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

                                              • babylon
                                                /#18888491

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

                                                • Druu
                                                  /#18888777

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

                                                  • babylon
                                                    /#18889043

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

                                                    • Druu
                                                      /#18889441

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

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

                                                      • babylon
                                                        /#18889561

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

                                                    • 0xd34df00d
                                                      /#18891073

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

                                                      • babylon
                                                        /#18891309

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


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

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

                                • 0xd34df00d
                                  /#18888173 / +1

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

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

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

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

                                  • DASM
                                    /#18888195

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

                                    • 0xd34df00d
                                      /#18888207

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

                                      Вы про тот же 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.»

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

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

                                      • DASM
                                        /#18888301

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

                                        • 0xd34df00d
                                          /#18888323

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

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

                                    • Druu
                                      /#18888337

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

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

                                      • DASM
                                        /#18888357

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

                                        • Druu
                                          /#18888415

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

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

                                        • 0xd34df00d
                                          /#18891109

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

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

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

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

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

                                    • F376
                                      /#18888365

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

                                      • DASM
                                        /#18888389

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

                                        • Druu
                                          /#18888417

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

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

        • Yuuri
          /#18886901

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

          • 0xd34df00d
            /#18887177

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

            • 0xd34df00d
              /#18891085

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

      • Gutt
        /#18885683

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

    • SinsI
      /#18885103

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

      • 0xd34df00d
        /#18885181

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

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

        • Druu
          /#18885473

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

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

          • 0xd34df00d
            /#18887553

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

            • Druu
              /#18888169

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

      • bvdmitri
        /#18885297 / +1

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

        • GoldJee
          /#18885463

          Вот иллююстрация на 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
          

          • TheKnight
            /#18885695 / +1

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

          • Akon32
            /#18885773

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

    • vics001
      /#18885137

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

    • Yuuri
      /#18886911

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

    • amarao
      /#18887545 / +2

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

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

      • SinsI
        /#18888353 / +1

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

        • evocatus
          /#18890961

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

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

  2. harlong
    /#18885001

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

    • mk2
      /#18885021

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

      • harlong
        /#18885031

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

    • netch80
      /#18887175 / +2

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

      • mk2
        /#18887847

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

      • funca
        /#18888269

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

  3. picul
    /#18885125

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

  4. Gaernebjorn
    /#18885195

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

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

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

    • Furax
      /#18885301 / -1

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

      • Gaernebjorn
        /#18885811

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


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

        • Chamie
          /#18886313

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

    • Gutt
      /#18885691

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

  5. Zuy
    /#18885273 / +1

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

    • Furax
      /#18885303

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

      • Antervis
        /#18885337

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

      • Zuy
        /#18885521 / +1

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

        • Furax
          /#18886903

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

    • robert_ayrapetyan
      /#18885305

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

      • khim
        /#18885333

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

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

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

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

        • Antervis
          /#18885341

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

          • Rsa97
            /#18885363

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

            • khim
              /#18885431

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

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

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

              • roscomtheend
                /#18887481 / +1

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

          • yurrig
            /#18885375

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

            • khim
              /#18885423

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

              • yurrig
                /#18885459

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

                • khim
                  /#18885581 / +1

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

                  • yurrig
                    /#18885661

                    Поясните?

                    • khim
                      /#18885675 / +1

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

                      • yurrig
                        /#18885729

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

                        • khim
                          /#18886397

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

                          • Druu
                            /#18886497

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

                            • khim
                              /#18886801

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

                              • Druu
                                /#18886969

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

                                • khim
                                  /#18887189

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

                                  • Druu
                                    /#18888215

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


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

          • khim
            /#18885415 / -1

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

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

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

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

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

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

      • Ryppka
        /#18885481

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

        • khim
          /#18885583

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

          • Ryppka
            /#18885783

            SPARK64? POWER*?

            • khim
              /#18886337

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

      • Zuy
        /#18885485

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

        • robert_ayrapetyan
          /#18885497

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

          • Zuy
            /#18885595

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

          • khim
            /#18885631

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

            • robert_ayrapetyan
              /#18887629

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

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

              • khim
                /#18887687 / +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 выделяет и на корректность чего, соотвественно, можно положиться. Вот в тот момент, когда выяснилось, что указатель можно превратить в число и потом — обратно в указатель… вся схема и «провисла:»…

                • robert_ayrapetyan
                  /#18887851

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

                  • khim
                    /#18887879

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

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

                    • Ryppka
                      /#18888023

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

                      • khim
                        /#18888123

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

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

        • Furax
          /#18885511

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

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

        • khim
          /#18885599

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

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

          • Zuy
            /#18885633 / -1

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

            • khim
              /#18885663

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

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

              • Druu
                /#18885831

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

                • khim
                  /#18886351

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

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

                  • Druu
                    /#18886389

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

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

                    • khim
                      /#18887709 / +1

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

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

                      • Druu
                        /#18888233

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

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


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

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


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

                        • khim
                          /#18888321

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

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

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

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

                          • Druu
                            /#18888341

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

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

                            • khim
                              /#18892401

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

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

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

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

                              • Druu
                                /#18892499

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

    • RadicalDreamer
      /#18885615 / +1

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

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

    • Goldseeker
      /#18885871

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

  6. Duny
    /#18885289

    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.

  7. daiver19
    /#18885299

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

    • BalinTomsk
      /#18888011 / +1

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

      • daiver19
        /#18888129 / +1

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

        • Akon32
          /#18889087

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

  8. COCATb
    /#18885309 / +1

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

  9. TimeCoder
    /#18885355

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

    • jmdorian
      /#18885547 / +2

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

    • Akon32
      /#18885819

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


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

    • stychos
      /#18888395

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

  10. Sdima1357
    /#18885835

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

  11. WinPooh73
    /#18886161

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

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

  12. x-foby
    /#18886905

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

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

    • Free_ze
      /#18887617 / +1

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

      • x-foby
        /#18887669 / +1

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

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

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

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

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

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

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

      • Druu
        /#18888251

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

  13. gospodinputin
    /#18886907

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

  14. technic93
    /#18887083

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


    typedef Object* ObjectPtr

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

    • technic93
      /#18887107

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


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

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

    • Inine
      /#18890419 / +1

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

  15. Nick_Shl
    /#18887259

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

    • MacIn
      /#18887747

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

  16. ne_zabudka
    /#18890779

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