Каковы барьеры для понимания указателей и что можно сделать, чтобы их преодолеть? [закрыто]

449

Почему указатели являются таким ведущим фактором путаницы для многих новых и даже старых студентов уровня колледжа в C или C ++? Существуют ли какие-либо инструменты или мыслительные процессы, которые помогли вам понять, как работают указатели на уровне переменной, функции и за ее пределами?

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

David McGraw
источник
20
Тезис этого вопроса заключается в том, что указатели трудно понять. Этот вопрос не дает никаких доказательств того, что указатели сложнее понять, чем что-либо еще.
bmargulies
14
Может быть, я что-то упускаю (потому что я кодирую на языках GCC), но я всегда думал, что указатели в памяти - это структура Key-> Value. Поскольку передача больших объемов данных в программе обходится дорого, вы создаете структуру (значение) и передаете ее указатель / ссылку (ключ), поскольку ключ представляет собой гораздо меньшее представление более крупной структуры. Сложная часть - это когда вам нужно сравнить два указателя / ссылки (сравниваете ли вы ключи или значения), что требует больше работы, чтобы разбить данные, содержащиеся в структуре (значении).
Эван Плейс,
2
@ Wolfpack'08 "Мне кажется, что память в адресе всегда будет int." - Тогда вам должно показаться, что ничто не имеет типа, поскольку все они - просто биты в памяти. «На самом деле тип указателя - это тип переменной, на которую указывает указатель» - Нет, тип указателя - это указатель на тип переменной, на которую указывает указатель - что является естественным и должно быть очевидным.
Джим Балтер
2
Мне всегда было интересно, что так трудно понять в том факте, что переменные (и функции) - это просто блоки памяти, а указатели - это переменные, хранящие адреса памяти. Эта, возможно, слишком практичная модель мышления может не впечатлить всех поклонников абстрактных концепций, но она прекрасно помогает понять, как работают указатели.
Кристиан Рау
8
Короче говоря, учащиеся, вероятно, не понимают, потому что они не понимают правильно или вообще не понимают, как работает память компьютера в целом и, в частности, C-модель памяти . Эта книга « Программирование с нуля» дает очень хороший урок на эти темы.
Аббафей

Ответы:

745

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

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

Я добавил немного кода Delphi внизу и несколько комментариев, где это уместно. Я выбрал Delphi, поскольку мой другой основной язык программирования, C #, не демонстрирует такие вещи, как утечки памяти, таким же образом.

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

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


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

type
    THouse = class
    private
        FName : array[0..9] of Char;
    public
        constructor Create(name: PChar);
    end;

Когда вы инициализируете объект дома, имя, данное конструктору, копируется в приватное поле FName. Есть причина, по которой он определяется как массив фиксированного размера.

В памяти будут некоторые накладные расходы, связанные с распределением домов, я проиллюстрирую это ниже следующим образом:

--- [ttttNNNNNNNNNN] ---
     ^ ^
     | |
     | + - массив FName
     |
     + - накладные расходы

Область "tttt" является дополнительной, обычно ее будет больше для различных типов сред выполнения и языков, таких как 8 или 12 байтов. Крайне важно, чтобы любые значения, хранящиеся в этой области, никогда не изменялись ничем, кроме распределителя памяти или подпрограмм основной системы, иначе вы рискуете аварийно завершить работу программы.


Выделить память

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

Другими словами, предприниматель выберет место.

THouse.Create('My house');

Расположение памяти:

--- [ttttNNNNNNNNNN] ---
    1234Мой дом

Держите переменную с адресом

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

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...

Расположение памяти:

    час
    v
--- [ttttNNNNNNNNNN] ---
    1234Мой дом

Скопировать значение указателя

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

Примечание. Обычно это та концепция, которую я больше всего объясняю людям: два указателя не означают два объекта или блока памяти.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1
    v
--- [ttttNNNNNNNNNN] ---
    1234Мой дом
    ^
    h2

Освобождая память

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

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    h := nil;

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

Расположение памяти:

    ч <- +
    v + - перед свободным
--- [ttttNNNNNNNNNN] --- |
    1234Мой дом <- +

    h (теперь нигде не указывает) <- +
                                + - после бесплатно
---------------------- | (обратите внимание, память может еще
    xx34Мой дом <- + содержит некоторые данные)

Висячие указатели

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

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    ... // forgot to clear h here
    h.OpenFrontDoor; // will most likely fail

Использование hпосле вызова .Free может сработать, но это просто удача. Скорее всего, он потерпит неудачу на месте клиента в середине критической операции.

    ч <- +
    v + - перед свободным
--- [ttttNNNNNNNNNN] --- |
    1234Мой дом <- +

    ч <- +
    v + - после бесплатно
---------------------- |
    xx34Мой дом <- +

Как вы можете видеть, h по-прежнему указывает на остатки данных в памяти, но, поскольку они могут быть неполными, использование их, как и раньше, может привести к сбою.


Утечка памяти

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

var
    h: THouse;
begin
    h := THouse.Create('My house');
    h := THouse.Create('My house'); // uh-oh, what happened to our first house?
    ...
    h.Free;
    h := nil;

Здесь мы переписали содержимое hпеременной с адресом нового дома, но старый все еще стоит ... где-то. После этого кода нет возможности добраться до этого дома, и он останется стоять. Другими словами, выделенная память будет оставаться выделенной до тех пор, пока приложение не закроется, после чего операционная система отключит ее.

Расположение памяти после первого выделения:

    час
    v
--- [ttttNNNNNNNNNN] ---
    1234Мой дом

Расположение памяти после второго выделения:

                       час
                       v
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234Мой дом 5678Мой дом

Более распространенный способ получить этот метод - просто забыть освободить что-то, а не перезаписать это, как указано выше. В терминах Delphi это произойдет с помощью следующего метода:

procedure OpenTheFrontDoorOfANewHouse;
var
    h: THouse;
begin
    h := THouse.Create('My house');
    h.OpenFrontDoor;
    // uh-oh, no .Free here, where does the address go?
end;

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

Расположение памяти:

    ч <- +
    v + - до потери указателя
--- [ttttNNNNNNNNNN] --- |
    1234Мой дом <- +

    h (теперь нигде не указывает) <- +
                                + - после потери указателя
--- [ttttNNNNNNNNNN] --- |
    1234Мой дом <- +

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


Освобождение памяти, но сохранение (теперь недействительной) ссылки

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

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

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

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1.Free;
    h1 := nil;
    h2.OpenFrontDoor; // uh-oh, what happened to our house?

Здесь дом был снесен, по ссылке h1, и, хотя он h1был очищен, h2все еще имеет старый, устаревший адрес. Доступ к дому, который больше не стоит, может или не может работать.

Это вариант висящего указателя выше. Смотрите его расположение памяти.


Переполнение буфера

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

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

Таким образом, этот код:

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := THouse.Create('My other house somewhere');
                         ^-----------------------^
                          longer than 10 characters
                         0123456789 <-- 10 characters

Расположение памяти после первого выделения:

                        h1
                        v
----------------------- [ttttNNNNNNNNNN]
                        5678Мой дом

Расположение памяти после второго выделения:

    h2 h1
    ст
--- [ttttNNNNNNNNNN] ---- [ttttNNNNNNNNNN]
    1234Мой другой дом где-нибудь
                        ^ --- + - ^
                            |
                            + - перезаписано

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


Связанные списки

Когда вы следуете по адресу на листе бумаги, вы попадаете в дом, и в этом доме есть еще один листок бумаги с новым адресом для следующего дома в цепочке и так далее.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;

Здесь мы создаем ссылку от нашего дома до нашей каюты. Мы можем следовать по цепочке до тех пор, пока дом не будет NextHouseупоминаться, что означает, что он последний. Чтобы посетить все наши дома, мы могли бы использовать следующий код:

var
    h1, h2: THouse;
    h: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;
    ...
    h := h1;
    while h <> nil do
    begin
        h.LockAllDoors;
        h.CloseAllWindows;
        h := h.NextHouse;
    end;

Расположение памяти (добавлено NextHouse как ссылка в объекте, отмеченное четырьмя LLLL на диаграмме ниже):

    h1 h2
    ст
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234Дом + 5678Кабина +
                   | ^ |
                   + -------- + * (без ссылки)

В общих чертах, что такое адрес памяти?

Адрес памяти в основных терминах просто число. Если вы думаете о памяти как о большом массиве байтов, самый первый байт имеет адрес 0, следующий - адрес 1 и так далее. Это упрощено, но достаточно хорошо.

Итак, это расположение памяти:

    h1 h2
    ст
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234Мой дом 5678Мой дом

Может иметь эти два адреса (самый левый - это адрес 0):

  • h1 = 4
  • h2 = 23

Это означает, что наш приведенный выше список может выглядеть так:

    h1 (= 4) h2 (= 28)
    ст
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234Дом 0028 5678Кабина 0000
                   | ^ |
                   + -------- + * (без ссылки)

Обычно адрес, который нигде не указывает, является нулевым адресом.


В общих чертах, что такое указатель?

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

Лассе В. Карлсен
источник
59
Переполнение буфера смешно. «Сосед приходит домой, раскрывает свой череп, скользит по твоему барахлу и судится с тобой в забвении».
gtd
12
Это хорошее объяснение концепции, конечно. Концепция не является той вещью, которую я нахожу запутывающей относительно указателей, таким образом, это целое эссе было немного потрачено впустую.
Бретон
10
Но просто чтобы спросить, что вас смущает в отношении указателей?
Лассе В. Карлсен
11
Я повторно посетил этот пост с тех пор, как вы написали ответ. Ваше разъяснение с кодом превосходно, и я благодарен вам за то, что вы вернулись к нему, чтобы добавить / уточнить другие мысли. Браво Лассе!
Дэвид Макгроу
3
Невозможно, чтобы одна страница текста (независимо от того, сколько она длилась) могла бы суммировать все нюансы управления памятью, ссылок, указателей и т. Д. Поскольку 465 человек проголосовали за это, я бы сказал, что это достаточно хорошо стартовая страница на информацию. Есть еще что-то, чтобы учиться? Конечно, когда это не так?
Лассе В. Карлсен
153

В моем первом классе Comp Sci мы выполнили следующее упражнение. Конечно, это был лекционный зал с примерно 200 студентами ...

Профессор пишет на доске: int john;

Джон встает

Профессор пишет: int *sally = &john;

Салли встает, указывает на Джона

Профессор: int *bill = sally;

Билл встает, указывает на Джона

Профессор: int sam;

Сэм встает

Профессор: bill = &sam;

Билл теперь указывает на Сэма.

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

Tryke
источник
5
Я не думаю, что я понял это неправильно. Мое намерение состояло в том, чтобы изменить значение указываемой переменной с Джона на Сэма. Представить людей немного сложнее, потому что похоже, что вы меняете значение обоих указателей.
Tryke
24
Но причина, по которой это сбивает с толку, заключается в том, что Джон не встал со своего места, а Сэм сел, как мы могли себе представить. Это больше похоже на то, как Сэм подошел, сунул руку в Джона и клонировал программирование Сэма в тело Джона, как переплетение Гюго в перезагруженной матрице.
Бретон
59
Больше похоже на то, как Сэм садится на место Джона, а Джон плывет по комнате, пока не наткнется на что-то критическое и вызовет сегфо
Just_Wes
2
Я лично считаю этот пример излишне сложным. Мой профессор велел мне указать на свет и сказал: «Твоя рука - указатель на световой объект».
Celeritas
Проблема с примерами такого типа заключается в том, что указатель на X и X не совпадает. И это не изображается с людьми.
Исаак Некиттепас
124

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

Wilka
источник
15
Мне действительно это нравится. Нетрудно видеть, что двойная запись гиперссылки не приводит к появлению двух веб-сайтов (так же, как int *a = bне делает две копии *b).
детально
4
Это на самом деле очень интуитивно понятно, и каждый должен иметь к этому отношение. Хотя есть много сценариев, где эта аналогия разваливается. Отлично подходит для быстрого ознакомления. +1
Брайан Виггинтон
Ссылка на страницу, которая открывается дважды, обычно создает два почти полностью независимых экземпляра этой веб-страницы. Я думаю, что гиперссылка может быть хорошей аналогией с конструктором, но не с указателем.
Уткан Гезер
@ThoAppelsin Не обязательно верно, если вы, например, обращаетесь к статической html-странице, вы обращаетесь к одному файлу на сервере.
Dramzy
5
Вы переосмысливаете это. Гиперссылки указывают на файлы на сервере, это аналогия.
Драмзи
48

Похоже, причина, по которой указатели смущают многих людей, заключается в том, что они в основном не имеют никакого опыта в компьютерной архитектуре или вообще не имеют его. Поскольку многие, похоже, не имеют представления о том, как на самом деле реализованы компьютеры (машины), работа в C / C ++ кажется чуждой.

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

Все, что требует чуть более простого дополнения, будет включать указатели, и они обязательно получат это.

JSN
источник
2
Интересно. Не знаю, как начать идти об этом, хотя. Любые ресурсы, чтобы поделиться?
Каролис
1
Согласен. Например, я научился программировать на ассемблере до C, и, зная, как работают регистры, учить указатели было легко. На самом деле, не было много обучения, все это было очень естественно.
Милан Бабушков
Возьмите базовый процессор, скажите что-нибудь, что запускает газонокосилки или посудомоечные машины, и реализуйте его. Или очень очень базовое подмножество ARM или MIPS. Оба из них имеют очень простой ISA.
Даниэль Голдберг
1
Возможно, стоит отметить, что этот образовательный подход защищал / практиковал сам Дональд Кнут. «Искусство компьютерного программирования» Кнута описывает простую гипотетическую архитектуру и предлагает студентам реализовать решения для практических задач на гипотетическом языке ассемблера для этой архитектуры. Как только это стало практически осуществимым, некоторые студенты, читающие книги Кнута, фактически реализуют его архитектуру как виртуальную машину (или используют существующую реализацию) и фактически запускают свои решения. ИМО это отличный способ учиться, если у вас есть время.
WeirdlyCheezy
4
@ Люк, я не думаю, что так легко понять людей, которые просто не могут понять указатели (или, если быть более точным, косвенность вообще). Вы в основном предполагаете, что люди, которые не понимают указатели в C, смогут начать изучать ассемблер, понять основную архитектуру компьютера и вернуться к C с пониманием указателей. Это может быть правдой для многих, но согласно некоторым исследованиям, кажется, что некоторые люди по своей сути не могут понять косвенность, даже в принципе (мне все еще трудно в это поверить, но, возможно, мне просто повезло с моими "учениками" «).
Луаан
27

Почему указатели являются таким ведущим фактором путаницы для многих новых и даже старых студентов уровня колледжа на языке C / C ++?

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

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

Адреса ящиков. Я помню, когда я учился программировать BASIC в микрокомпьютерах, там были эти красивые книги с играми, и иногда вам приходилось вставлять значения в конкретные адреса. У них было изображение группы коробок, постепенно помеченных 0, 1, 2 ... и было объяснено, что только одна маленькая вещь (байт) могла поместиться в этих коробках, и их было много - некоторые компьютеры было целых 65535! Они были рядом друг с другом, и у них всех был адрес.

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

Для дрели? Создайте структуру:

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

Тот же пример, что и выше, за исключением C:

// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

Вывод:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

Возможно, это объясняет некоторые из основ на примере?

Джош
источник
+1 за "не понимая, как физически заложена память". Я пришел к Си из языка ассемблера, и концепция указателей была очень естественной и простой; и я видел людей с языковым опытом более высокого уровня, которые пытаются это понять. Что еще хуже, синтаксис вводит в заблуждение (указатели на функции!), Поэтому одновременное изучение концепции и синтаксиса является причиной неприятностей.
Брендан
4
Я знаю это старый пост, но было бы здорово, если бы к сообщению был добавлен вывод предоставленного кода.
Джош
Да, это похоже на алгебру (хотя алгебра обладает дополнительным пониманием того, что их «переменные» неизменны). Но примерно половина людей, которых я знаю, на практике не разбираются в алгебре. Это просто не для них. Они знают все эти «уравнения» и предписания, чтобы получить результат, но применяют их несколько случайно и неуклюже. И они не могут расширять их для своих собственных целей - это просто какой-то неизменный, неразложимый черный ящик для них. Если вы понимаете алгебру и способны эффективно ее использовать, вы уже намного впереди - даже среди программистов.
Луаан
24

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

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

Учебник по указателям и массивам в C: Глава 3 - Указатели и строки

int puts(const char *s);

На данный момент игнорируем const.параметр, на который передается параметр puts(), это указатель, то есть значение указателя (поскольку все параметры в C передаются по значению), а значение указателя является адресом, на который он указывает, или, просто , адрес. Таким образом, когда мы пишем, puts(strA);как мы видели, мы передаем адрес strA [0].

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

Даже если вы являетесь разработчиком VB .NET или C # (как и я) и никогда не используете небезопасный код, все равно стоит понять, как работают указатели, иначе вы не поймете, как работают ссылки на объекты. Тогда у вас будет распространенное, но ошибочное мнение, что передача ссылки на объект в метод копирует объект.

оборота Киралесса
источник
Мне интересно, какой смысл иметь указатель. В большинстве блоков кода, с которыми я сталкиваюсь, указатель виден только в его объявлении.
Wolfpack'08
@ Wolfpack'08 ... что ?? Какой код вы смотрите ??
Кайл Стрэнд
@KyleStrand Позвольте мне взглянуть.
Wolfpack'08
19

«Учебник Теда Дженсена по указателям и массивам в Си» я нашел отличным ресурсом для изучения указателей. Он разделен на 10 уроков, начиная с объяснения того, что такое указатели (и для чего они нужны) и заканчивая указателями на функции. http://home.netcom.com/~tjensen/ptr/cpoint.htm

Далее Beej's Guide по сетевому программированию обучает API сокетов Unix, из которого вы можете начать делать действительно забавные вещи. http://beej.us/guide/bgnet/

Тед Персиваль
источник
1
Я второй учебник Теда Дженсена. Он разбивает указатели на уровень детализации, это не слишком подробно, ни одна книга, которую я читал, не делает. Очень полезно! :)
Дэйв Галлахер
12

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

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

widget->wazzle.fizzle = fazzle.foozle->wazzle;

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

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

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

Дерек Парк
источник
10

Я решил добавить аналогию к этому списку, которая показалась мне очень полезной при объяснении указателей (в прошлом) в качестве преподавателя информатики; во-первых, давайте:


Установите сцену :

Рассмотрим парковку с 3-мя местами, эти номера пронумерованы:

-------------------
|     |     |     |
|  1  |  2  |  3  |
|     |     |     |

В некотором смысле это похоже на ячейки памяти, они последовательные и смежные ... вроде как массив. Сейчас в них нет машин, так что это как пустой массив ( parking_lot[3] = {0}).


Добавить данные

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

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |R| | |G| |
| o-o | o-o | o-o |

Эти машины все тот же тип (автомобиль) , так что один способ думать о том , что наши машины какие - то данные (скажем int) , но они имеют разные значения ( blue, red, green, которые могут быть цвета enum)


Введите указатель

Теперь, если я отвезу вас на эту парковку и попрослю вас найти мне синюю машину, вы вытяните один палец и при помощи него укажите на синюю машину в месте 1. Это все равно, что взять указатель и присвоить его адресу памяти. ( int *finger = parking_lot)

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


Переназначение указателя

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

Указатель физически не изменился, это все еще ваш палец, изменились только те данные, которые он мне показывал. (адрес "места для парковки")


Двойные указатели (или указатель на указатель)

Это работает с более чем одним указателем. Я могу спросить, где находится указатель, указывающий на красную машину, и вы можете использовать другую руку и указать пальцем на первый палец. (это как int **finger_two = &finger)

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


Свисающий указатель

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

   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |

Ваш указатель все еще указывает на то, где находилась красная машина, но ее больше нет. Скажем, новая машина останавливается там ... оранжевая машина. Теперь, если я снова спрошу вас «где красная машина», вы все еще указываете туда, но теперь вы ошибаетесь. Это не красная машина, это оранжевая.


Арифметика указателей

Итак, вы все еще указываете на второе место для парковки (сейчас занято автомобилем Orange)

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |

Ну, у меня теперь новый вопрос ... Я хочу знать цвет машины на следующем месте парковки. Вы можете видеть, что вы указываете на точку 2, поэтому вы просто добавляете 1 и указываете на следующую точку. ( finger+1), теперь, так как я хотел знать, какие данные были там, вы должны проверить это место (не только палец), чтобы вы могли отложить указатель ( *(finger+1)), чтобы увидеть, что там есть зеленая машина (данные в этом месте )

Майк
источник
Только не используйте слово «двойной указатель». Указатели могут указывать на что угодно, поэтому очевидно, что у вас могут быть указатели, указывающие на другие указатели. Они не являются двойными указателями.
gnasher729
Я думаю, что здесь не хватает того, что сами «пальцы», продолжая вашу аналогию, каждый «занимают место для парковки». Я не уверен, что людям трудно понять указатели на высоком уровне абстракции вашей аналогии, это понимание, что указатели - это изменчивые вещи, которые занимают места в памяти, и насколько это полезно, что, кажется, уклоняется от людей.
Эммет
1
@Emmet - я не согласен с тем, что в указатели WRT можно добавить гораздо больше, но я читаю вопрос: "without getting them bogged down in the overall concept"как понимание высокого уровня. И к вашему мнению: "I'm not sure that people have any difficulty understanding pointers at the high level of abstraction"- вы были бы очень удивлены, как много людей не понимают указателей даже до этого уровня
Майк
Есть ли смысл в том, чтобы провести аналогию с автомобильным пальцем для человека (одним или несколькими пальцами - и генетической аномалией, которая может позволить каждому из них указывать в любом направлении!), Сидевшей в одной из машин, указывающей на другую машину (или наклоняется, указывая на пустошь рядом с участком как «неинициализированный указатель», или целую руку, указывающую на ряд пробелов как «массив указателей фиксированного размера [5]», или свернутую в ладонь «нулевой указатель» это указывает куда-то, где известно, что НИКОГДА нет машины) ... 8-)
SlySven
Ваше объяснение было заметным и хорошим для начинающего.
Ятендра Раторе
9

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

Трудность, по крайней мере та, с которой я сталкивался в прошлом и с которой сталкивались другие, заключается в том, что управление указателями в C / C ++ может быть излишне запутанным.

Мэтт Митчелл
источник
9

Пример учебника с хорошим набором диаграмм очень помогает в понимании указателей .

Джоэл Спольски делает несколько хороших замечаний о понимании указателей в своей статье « Партизанское руководство по интервью» :

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

David
источник
8

Проблема с указателями не в концепции. Это исполнение и язык вовлечены. Дополнительная путаница приводит к тому, что учителя предполагают, что это КОНЦЕПЦИЯ указателей труднее, а не жаргон или запутанный беспорядок C и C ++ делает концепцию. Огромные усилия прилагаются для объяснения концепции (как в принятом ответе на этот вопрос), и она в значительной степени просто напрасна на кого-то вроде меня, потому что я уже все это понимаю. Это просто объясняет не ту часть проблемы.

Чтобы дать вам представление о том, откуда я родом, я тот, кто прекрасно понимает указатели, и я могу грамотно использовать их на языке ассемблера. Потому что на языке ассемблера они не называются указателями. Они называются адресами. Когда дело доходит до программирования и использования указателей в C, я допускаю много ошибок и очень запутываюсь. Я до сих пор не разобрался в этом. Позвольте привести пример.

Когда API говорит:

int doIt(char *buffer )
//*buffer is a pointer to the buffer

чего он хочет?

это могло бы хотеть:

число, представляющее адрес буфера

(Чтобы дать это, я говорю doIt(mybuffer), или doIt(*myBuffer)?)

число, представляющее адрес к адресу в буфере

(это doIt(&mybuffer)или doIt(mybuffer)или doIt(*mybuffer)?)

число, представляющее адрес по адресу к адресу в буфере

(может быть doIt(&mybuffer). или это doIt(&&mybuffer)? или даже doIt(&&&mybuffer))

и так далее, и используемый язык не делает это настолько ясным, потому что он включает в себя слова «указатель» и «ссылка», которые не имеют такого большого значения и ясности для меня, как «x содержит адрес для y» и « эта функция требует адреса к y ". Ответ дополнительно зависит от того, с какого черта «mybuffer» должен начинаться, и что он намерен делать с ним. Язык не поддерживает уровни вложенности, которые встречаются на практике. Например, когда мне нужно передать «указатель» на функцию, которая создает новый буфер, и он изменяет указатель так, чтобы он указывал на новое местоположение буфера. Действительно ли он хочет указатель или указатель на указатель, чтобы он знал, куда идти, чтобы изменить содержимое указателя. Большую часть времени я просто должен догадаться, что подразумевается под "

«Указатель» просто перегружен. Указатель является адресом для значения? или это переменная, которая содержит адрес значения. Когда функция хочет указатель, она хочет адрес, который содержит переменная указателя, или она хочет адрес переменной указателя? Я смущен.

бретонский
источник
Я видел, что это объясняется так: если вы видите объявление указателя как double *(*(*fn)(int))(char), то результатом оценки *(*(*fn)(42))('x')будет a double. Вы можете снять уровни оценки, чтобы понять, какими должны быть промежуточные типы.
Бернд Джендриссек
@BerndJendrissek Не уверен, что я следую. Каков результат оценки (*(*fn)(42))('x') тогда?
Бретон
Вы получаете вещь (давайте назовем это x), где, если вы оцениваете *x, вы получаете двойную.
Бернд Джендриссек
@BerndJendrissek Это должно объяснить что-то о указателях? Я не понимаю В чем ваша точка зрения? Я снял слой и не получил никакой новой информации о каких-либо промежуточных типах. Что это объясняет о том, что определенная функция собирается принять? Какое это имеет отношение к чему-либо?
Бретон
Возможно, сообщение в этом объяснении (и это не мое, я хотел бы найти то, где я впервые увидел это) состоит в том, чтобы думать об этом меньше с точки зрения того, что fn есть, и больше с точки зрения того, что вы можете сделать сfn
Бернд Джендриссек
8

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

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

И конечно, что они трудны для понимания, опасны и полумагичны.

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

Я попытался написать объяснение этому несколько месяцев назад в этом посте - надеюсь, это кому-нибудь поможет.

(Обратите внимание, что до того, как кто-нибудь станет со мной педантичен, да, стандарт C ++ говорит, что указатели представляют адреса памяти. Но он не говорит, что «указатели являются адресами памяти и ничем иным, как адресами памяти, и могут использоваться или считаться взаимозаменяемыми с памятью». адреса ». Различие важно)

jalf
источник
В конце концов, нулевой указатель не указывает на нулевой адрес в памяти, хотя его «значение» C равно нулю. Это совершенно отдельная концепция, и если вы справитесь с ней неправильно, вы можете в конечном итоге обратиться (и разыменовать) к чему-то, чего вы не ожидали. В некоторых случаях это может быть даже нулевой адрес в памяти (особенно теперь, когда адресное пространство обычно плоское), но в других это может быть опущено оптимизирующим компилятором как неопределенное поведение, или доступ к некоторой другой части памяти, которая связана с "нулем" для данного типа указателя. Веселье наступает.
Луаан
Не обязательно. Вы должны быть в состоянии смоделировать компьютер в своей голове, чтобы указатели имели смысл (а также для отладки других программ). Не каждый может это сделать.
Турбьёрн Равн Андерсен
5

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

Когда вы впервые видите указатель, вы на самом деле не получаете то, что находится в этом месте памяти. "Что вы имеете в виду, он имеет адрес ?"

Я не согласен с тем, что «вы либо получаете их, либо не получаете».

Их становится легче понять, когда вы начинаете находить для них реальное применение (например, не передавать большие структуры в функции).

Baltimark
источник
5

Причина, по которой это так трудно понять, не в том, что это сложная концепция, а в том, что синтаксис противоречив .

   int *mypointer;

Сначала вы узнали, что крайняя левая часть создания переменной определяет тип переменной. Объявление указателя не работает так в C и C ++. Вместо этого они говорят, что переменная указывает на тип слева. В этом случае: *mypointer указывает на int.

Я не полностью понял указатели, пока не попробую использовать их в C # (с небезопасным), они работают точно так же, но с логическим и последовательным синтаксисом. Указатель сам по себе является типом. Здесь mypointer является указателем на int.

  int* mypointer;

Даже не заводите меня на указатели функций ...

burrrr
источник
2
На самом деле, оба ваших фрагмента являются действительными C. В течение многих лет стиль C является наиболее распространенным. Например, второе немного более распространено в C ++.
RBerteig
1
Второй фрагмент не очень хорошо работает с более сложными объявлениями. И синтаксис не будет таким «непоследовательным», как только вы поймете, что правая часть объявления указателя показывает вам, что вы должны сделать с указателем, чтобы получить что-то, чей тип является атомарным спецификатором типа слева.
Бернд Джендриссек
2
int *p;имеет простое значение: *pявляется целым числом. int *p, **ppозначает: *pи **ppявляются целыми числами.
Майлз Рут
@MilesRout: Но это именно проблема. *pи не**pp являются целыми числами, потому что вы никогда не инициализировали или или указывать на что-либо. Я понимаю, почему некоторые люди предпочитают придерживаться этой грамматики, особенно потому, что некоторые крайние случаи и сложные случаи требуют от вас (хотя, тем не менее, вы можете тривиально обойти это во всех случаях, о которых я знаю) ... но я не думаю, что эти случаи более важны, чем тот факт, что обучение правильному выравниванию вводит в заблуждение новичков. Не говоря уже о некрасивом! :)ppp*pp
Гонки легкости на орбите
@LightnessRacesinOrbit Обучение правильному выравниванию далеко не вводит в заблуждение. Это единственный правильный способ научить этому. НЕ учить это вводит в заблуждение.
Майлз Рут
5

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

тото
источник
4

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

Например, следуя связанному списку: 1) начните со своей бумаги с адресом 2) перейдите по адресу на листе 3) откройте почтовый ящик, чтобы найти новый лист бумаги со следующим адресом на нем

В линейном связанном списке в последнем почтовом ящике ничего нет (конец списка). В круговом связанном списке последний почтовый ящик содержит адрес первого почтового ящика.

Обратите внимание, что на шаге 3 происходит разыменование, и вы можете потерпеть крах или ошибиться, если адрес недействителен. Предполагая, что вы можете подойти к почтовому ящику с неверным адресом, представьте, что там есть черная дыра или что-то еще, что выворачивает мир наизнанку :)

Кристофер Скотт
источник
Противоречие с аналогией номера почтового ящика состоит в том, что, хотя язык, изобретенный Деннисом Ричи, определяет поведение в терминах адресов байтов и значений, хранимых в этих байтах, язык, определенный стандартом C, предлагает "оптимизировать" реализации для использования поведенческого поведения. модель, которая является более сложной, но определяет различные аспекты модели способами, которые являются неоднозначными, противоречивыми и неполными.
суперкат
3

Я думаю, что главная причина, по которой у людей возникают проблемы, заключается в том, что, как правило, этому не учат интересно и увлекательно. Я хотел бы, чтобы лектор собрал 10 добровольцев из толпы и дал им по 1 метру каждого, чтобы они стояли в определенной конфигурации и использовали линейки, чтобы указывать друг на друга. Затем покажите арифметику указателей, перемещая людей (и куда они указывают своих правителей). Это был бы простой, но эффективный (и прежде всего запоминающийся) способ показать концепции, не слишком увязая в механике.

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

Wolfbyte
источник
2

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

bruceatk
источник
2

Я думаю, что это может быть проблема синтаксиса. Синтаксис C / C ++ для указателей кажется непоследовательным и более сложным, чем необходимо.

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

Иногда вы просто не можете видеть лес, пока не научитесь игнорировать деревья.

Уэйлон Флинн
источник
Проблема в основном в синтаксисе объявления C. Но использование указателя было бы легче, если (*p)бы это было так (p->), и поэтому мы имели бы p->->xвместо двусмысленного*p->x
MSalters
@MSalters Боже мой, ты шутишь, верно? Там нет никаких несоответствий там. a->bпросто значит (*a).b.
Майлз Рут
@Miles: Действительно, и по этой логике * p->xсредства , в * ((*a).b)то время как *p -> xсредства (*(*p)) -> x. Смешивание префиксных и постфиксных операторов приводит к неоднозначному анализу.
MSalters
@MSalters нет, потому что пробел не имеет значения. Это все равно, что сказать, что 1+2 * 3должно быть 9.
Майлз Рут
2

Путаница возникает из-за нескольких уровней абстракции, смешанных вместе в концепции «указатель». Программистов не смущают обычные ссылки в Java / Python, но указатели отличаются тем, что они раскрывают характеристики базовой архитектуры памяти.

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

Джошуа Фокс
источник
1
Интересно то, что указатели Си на самом деле не раскрывают какой-либо особенности базовой архитектуры памяти. Единственные различия между ссылками на Java и указателями на C состоят в том, что у вас могут быть сложные типы, включающие указатели (например, int *** или char * ( ) (void * )), существует арифметика указателей для массивов и указателей на структуру членов, наличие пустоты * и двойственность массива / указателя. Кроме того, они работают точно так же.
jpalecek
Хорошая точка зрения. Это арифметика указателя и возможность переполнения буфера - вырваться из абстракции, вырваться из актуальной области памяти - это делает это.
Джошуа Фокс
@jpalecek: Довольно легко понять, как работают указатели в реализациях, которые документируют их поведение с точки зрения базовой архитектуры. Говорить foo[i]означает идти в определенное место, двигаться вперед на определенное расстояние и видеть, что там. Что усложняет вещи, так это гораздо более сложный дополнительный уровень абстракции, который был добавлен стандартом исключительно для выгоды компилятора, но моделирует вещи таким образом, который плохо подходит для нужд программиста и компилятора.
суперкат
2

Мне нравилось объяснять это с точки зрения массивов и индексов - люди могут быть не знакомы с указателями, но они обычно знают, что такое индекс.

Итак, я говорю, представьте, что ОЗУ - это массив (а у вас всего 10 байтов ОЗУ):

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

Тогда указатель на переменную на самом деле является просто индексом (первого байта) этой переменной в ОЗУ.

Таким образом, если у вас есть указатель / индекс unsigned char index = 2, то значение, очевидно, является третьим элементом или числом 4. Указатель на указатель - это то место, где вы берете это число и используете его как сам индекс, например RAM[RAM[index]].

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

sashoalm
источник
1

Номер почтового ящика.

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

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

joel.neely
источник
1

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

Многие бывшие разработчики C-C ++ (которые никогда не понимали, что итераторы являются современным указателем до создания дампов языка) переходят на C # и все еще верят, что у них есть достойные итераторы.

Хм, проблема в том, что все, что делают итераторы, в полной мере не соответствует тому, чего стремятся достичь платформы времени исполнения (Java / CLR): новое, простое использование «все - как разработчик». Что может быть хорошо, но они сказали это однажды в фиолетовой книге, и они сказали это даже до и до C:

Косвенность.

Очень мощная концепция, но никогда, если вы делаете это все время. Итераторы полезны, поскольку они помогают с абстракцией алгоритмов, еще один пример. А время компиляции - это место для алгоритма, очень простого. Вы знаете код + данные или на другом языке C #:

IEnumerable + LINQ + Massive Framework = 300 МБ штрафных санкций за время выполнения при паршивом перетаскивании приложений через кучи экземпляров ссылочных типов.

«Le Pointer - это дешево».

рама-йка тоти
источник
4
Какое это имеет отношение к чему-либо?
Нил Уильямс
... что вы пытаетесь сказать, кроме того, что "статическое связывание - лучшая вещь когда-либо" и "я не понимаю, как работает нечто отличное от того, что я изучил ранее"?
Луаан
Луаан, ты не мог знать, что можно узнать, разобрав JIT в 2000 году, не так ли? То, что он заканчивается таблицей переходов, из таблицы указателей, как показано в 2000 году в Интернете в ASM, поэтому непонимание чего-либо другого может иметь другое значение: внимательное чтение является важным навыком, попробуйте еще раз.
рама-йка тоти
1

В некоторых ответах выше утверждалось, что «указатели не очень сложны», но они не обращались непосредственно к тому месту, где «указатель сложен!» происходит от. Несколько лет назад я обучал студентов-первокурсников CS (всего один год, так как я явно сосал их), и мне было ясно, что идея указателя не сложна. Трудно понять, почему и когда вам нужен указатель .

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

Бернд Джендриссек
источник
0

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

int* my_int_pointer;

Говорит, что my_int_pointer содержит адрес для местоположения, которое содержит int.

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

Grom
источник
0

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

http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 говорит об этом немного более связно, чем я. :-)

SarekOfVulcan
источник
-1: дескрипторы не являются указателями на указатели; они не являются указателями в любом смысле. Не путай их.
Ян Голдби
«Они не являются указателями в любом смысле», - умоляю я отличаться.
SarekOfVulcan
Указатель - это область памяти. Дескриптор - это любой уникальный идентификатор. Это может быть указатель, но это может быть также индекс в массиве или что-то еще в этом отношении. Ссылка, которую вы дали, представляет собой особый случай, когда дескриптор является указателем, но это не обязательно. См. Также parashift.com/c++-faq-lite/references.html#faq-8.8
Ян Голдби
Эта ссылка не поддерживает ваше утверждение о том, что они не являются указателями в каком-либо смысле - «Например, дескрипторы могут быть Fred **, где указатели на Fred * указывают ...» Я не думаю, что -1 был справедливым.
SarekOfVulcan
0

У каждого новичка C / C ++ есть та же самая проблема, и эта проблема возникает не потому, что «указатели трудно выучить», а «кто и как это объясняет». Некоторые учащиеся собирают это в устной форме, а некоторые визуально, и лучший способ объяснить это - использовать пример «поезд» (подходит для словесного и визуального примера).

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

Правин Кумар
источник