Вопрос:

What are the barriers to understanding pointers and what can be done to overcome them?

c++ c pointers

72787 просмотра

28 ответа

4114 Репутация автора

Why are pointers such a leading factor of confusion for many new, and even old, college level students in C or C++? Are there any tools or thought processes that helped you understand how pointers work at the variable, function, and beyond level?

What are some good practice things that can be done to bring somebody to the level of, "Ah-hah, I got it," without getting them bogged down in the overall concept? Basically, drill like scenarios.

Автор: David McGraw Источник Размещён: 08.08.2008 06:17

Ответы (28)


27 плюса

6567 Репутация автора

Почему указатели являются таким ведущим фактором путаницы для многих новых и даже старых студентов уровня колледжа на языке 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

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

Автор: Josh Размещён: 08.08.2008 06:19

9 плюса

25027 Репутация автора

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

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

Автор: Matt Mitchell Размещён: 08.08.2008 06:26

0 плюса

11524 Репутация автора

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

int* my_int_pointer;

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

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

Автор: grom Размещён: 08.08.2008 06:28

48 плюса

817 Репутация автора

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

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

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

Автор: JSN Размещён: 08.08.2008 06:29

3 плюса

24601 Репутация автора

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

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

Автор: Mike Minutillo Размещён: 08.08.2008 06:52

742 плюса

301457 Репутация автора

Решение

Pointers is a concept that for many can be confusing at first, in particular when it comes to copying pointer values around and still referencing the same memory block.

I've found that the best analogy is to consider the pointer as a piece of paper with a house address on it, and the memory block it references as the actual house. All sorts of operations can thus be easily explained.

I've added some Delphi code down below, and some comments where appropriate. I chose Delphi since my other main programming language, C#, does not exhibit things like memory leaks in the same way.

If you only wish to learn the high-level concept of pointers, then you should ignore the parts labelled "Memory layout" in the explanation below. They are intended to give examples of what memory could look like after operations, but they are more low-level in nature. However, in order to accurately explain how buffer overruns really work, it was important that I added these diagrams.

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


Предположим, что используемый ниже класс 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
                   | ^ |
                   + -------- + * (без ссылки)

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


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

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

Автор: Lasse Vågsæther Karlsen Размещён: 08.08.2008 06:58

9 плюса

8530 Репутация автора

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

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

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

Автор: David Размещён: 08.08.2008 07:54

150 плюса

775 Репутация автора

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

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

Джон встает

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

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

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

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

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

Сэм встает

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

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

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

Автор: Tryke Размещён: 08.08.2008 09:50

12 плюса

40564 Репутация автора

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

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

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

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

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

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

Автор: Derek Park Размещён: 10.08.2008 06:09

2 плюса

4783 Репутация автора

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

Автор: bruceatk Размещён: 10.08.2008 06:39

19 плюса

7068 Репутация автора

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

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

Автор: Ted Percival Размещён: 11.08.2008 04:21

4 плюса

1102 Репутация автора

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

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

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

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

Автор: Christopher Scott Размещён: 16.08.2008 03:28

124 плюса

21106 Репутация автора

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

Автор: Wilka Размещён: 16.08.2008 11:35

0 плюса

930 Репутация автора

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

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

Автор: SarekOfVulcan Размещён: 22.08.2008 06:25

5 плюса

4285 Репутация автора

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

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

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

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

Автор: Baltimark Размещён: 22.08.2008 07:19

1 плюс

26747 Репутация автора

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

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

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

Автор: joel.neely Размещён: 29.08.2008 06:47

7 плюса

12932 Репутация автора

Проблема с указателями не в концепции. Это исполнение и язык вовлечены. Дополнительная путаница приводит к тому, что учителя предполагают, что это КОНЦЕПЦИЯ указателей труднее, а не жаргон или запутанный беспорядок 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» должен начинаться, и что он намерен делать с ним. Язык не поддерживает уровни вложенности, которые встречаются на практике. Например, когда мне нужно передать «указатель» на функцию, которая создает новый буфер, и он изменяет указатель так, чтобы он указывал на новое местоположение буфера. Действительно ли он хочет указатель или указатель на указатель, чтобы он знал, куда идти, чтобы изменить содержимое указателя. Большую часть времени я просто должен догадаться, что подразумевается под "

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

Автор: Breton Размещён: 16.04.2009 11:53

2 плюса

15556 Репутация автора

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

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

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

Автор: Waylon Flinn Размещён: 17.04.2009 12:23

1 плюс

1370 Репутация автора

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

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

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

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

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

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

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

Автор: rama-jka toti Размещён: 17.04.2009 12:34

5 плюса

0 Репутация автора

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

   int *mypointer;

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

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

  int* mypointer;

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

Автор: burrrr Размещён: 17.04.2009 04:51

5 плюса

671 Репутация автора

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

Автор: toto Размещён: 29.04.2009 02:18

2 плюса

8657 Репутация автора

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

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

Автор: Joshua Fox Размещён: 04.05.2009 08:53

23 плюса

160686 Репутация автора

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

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

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

int puts(const char *s);

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

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

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

Автор: Ryan Lundy Размещён: 13.01.2010 02:01

8 плюса

212068 Репутация автора

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

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

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

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

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

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

Автор: jalf Размещён: 13.01.2010 02:34

0 плюса

123 Репутация автора

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

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

Автор: nurmurat Размещён: 02.11.2012 11:20

10 плюса

31147 Репутация автора

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


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

Рассмотрим парковку с 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)), чтобы увидеть, что там есть зеленая машина (данные в этом месте )

Автор: Mike Размещён: 05.04.2013 07:55

1 плюс

872 Репутация автора

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

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

Автор: Bernd Jendrissek Размещён: 28.04.2013 04:04

2 плюса

32198 Репутация автора

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

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

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

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

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

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

Автор: sashoalm Размещён: 22.05.2013 02:42
32x32