Как объяснить C-указателям (декларация против унарных операторов) новичкам?

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

int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);

Для абсолютного новичка вывод может быть неожиданным. В строке 2 он/она только что объявил * bar, чтобы быть и foo, но в строке 4 получается * bar фактически foo вместо & foo!

Смятение, можно сказать, связано с двусмысленностью символа *: в строке 2 он используется для объявления указателя. В строке 4 он используется как унарный оператор, который извлекает значение, на которое указывает указатель. Две разные вещи, верно?

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

Итак, как Керниган и Ричи объяснили это?

Унарный оператор * является оператором косвенной или разыменовывающей операции; когда применяется к указателю, он обращается к объекту, на который указывает указатель. [...]

Объявление указателя ip, int *ip предназначено как мнемоника; он говорит, что выражение *ip является int. Синтаксис объявления для переменной имитирует синтаксис выражений, в которых может отображаться переменная.

int *ip следует читать как "*ip вернет int"? Но почему же тогда назначение после объявления следовать этому шаблону? Что делать, если новичок хочет инициализировать переменную? int *ip = 1 (read: *ip вернет int и int is 1) не будет работать должным образом. Концептуальная модель просто не кажется последовательной. Я что-то пропустил?


Изменить: Он попытался обобщить ответы здесь.

Ответ 1

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

Во-первых, объясните, что объявление переменной не может содержать операторов (продемонстрируйте это, показывая, что размещение символа - или + в объявлении переменной просто вызывает ошибку). Затем покажем, что выражение (т.е. В правой части присваивания) может содержать операторы. Убедитесь, что ученик понимает, что выражение и объявление переменной - это два совершенно разных контекста.

Когда они понимают, что контексты разные, вы можете объяснить, что, когда символ * находится в объявлении переменной перед идентификатором переменной, это означает "объявить эту переменную как указатель". Тогда вы можете объяснить, что при использовании в выражении (как унарный оператор) символ * является "оператором разыменования", а это означает "значение по адресу", а не его более раннее значение.

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

В общем, нет смысла объяснять, что контексты разные. Если ученик не понимает, что контексты разные, они не могут понять, почему символ * может означать разные вещи.

Ответ 2

Причина сокращения:

int *bar = &foo;

в вашем примере может сбить с толку то, что он легко неправильно воспринимает его как эквивалент:

int *bar;
*bar = &foo;    // error: use of uninitialized pointer bar!

когда это на самом деле означает:

int *bar;
bar = &foo;

Написано так, что с объявлением переменной и назначением разделены, нет такого потенциала для путаницы, а использование & harr; Объявление parallelism, описанное в вашей цитате K & R, отлично работает:

  • Первая строка объявляет переменную bar, так что *bar является int.

  • Вторая строка присваивает адрес от foo до bar, делая *bar (an int) псевдоним для foo (также int).

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

Ответ 3

Короткие объявления

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

1. int a; a = 42;

int a;
a = 42;

Объявляем int с именем a. Затем мы инициализируем его, присваивая ему значение 42.

2. int a = 42;

Объявляем и int с именем a и даем ему значение 42. Оно инициализируется с помощью 42. Определение.

3. a = 43;

Когда мы используем переменные, мы говорим, что мы работаем над ними. a = 43 - операция присваивания. Мы присваиваем число 43 переменной a.

Говоря

int *bar;

мы объявляем bar как указатель на int. Говоря

int *bar = &foo;

мы объявляем bar и инициализируем его адресом foo.

После того, как мы инициализировали панель, мы можем использовать тот же оператор, звездочку, чтобы получить доступ к функциям foo и работать с ним. Без оператора мы получаем доступ и работаем по адресу, на который указывает указатель.

Кроме того, я позволяю картине говорить.

Что

Упрощенная ASCIIMATION о том, что происходит. (И здесь версия игрока, если вы хотите приостановить и т.д.)

ASCIIMATION

Ответ 4

Второе утверждение int *bar = &foo; может быть наглядно представлено в памяти как

   bar           foo
  +-----+      +-----+
  |0x100| ---> |  1  |
  +-----+      +-----+ 
   0x200        0x100

Теперь bar является указателем типа int, содержащим адрес & из foo. Используя унарный оператор *, мы предпочитаем извлекать значение, содержащееся в 'foo', используя указатель bar.

РЕДАКТИРОВАТЬ: Мой подход к начинающим заключается в объяснении memory address переменной, т.е.

Memory Address: Каждая переменная имеет связанный с ней адрес, предоставленный ОС. В int a;, &a является адресом переменной a.

Продолжайте объяснять основные типы переменных в C как

Types of variables: Переменные могут содержать значения соответствующих типов, но не адреса.

int a = 10; float b = 10.8; char ch = 'c'; 'a, b, c' are variables. 

Introducing pointers: Как сказано выше переменные, например

 int a = 10; // a contains value 10
 int b; 
 b = &a;      // ERROR

Возможно присвоить b = a, но не b = &a, поскольку переменная b может содержать значение, но не адрес, поэтому нам нужны указатели.

Pointer or Pointer variables : Если переменная содержит адрес, она называется переменной-указателем. Используйте * в объявлении, чтобы сообщить, что это указатель.

• Pointer can hold address but not value
• Pointer contains the address of an existing variable.
• Pointer points to an existing variable

Ответ 5

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

  • Прежде чем показывать какой-либо код, используйте диаграммы, эскизы или анимации, чтобы показать, как работают указатели.
  • При представлении синтаксиса объясните две разные роли символа звездочки. Многие учебные пособия отсутствуют или уклоняются от этой части. Возникает путаница ( "Когда вы разбиваете инициализированную декларацию указателя на объявление и последующее назначение, вы должны помнить об удалении *. - comp.lang. c FAQ) Я надеялся найти альтернативный подход, но я думаю, что это путь.

Вы можете написать int* bar вместо int *bar, чтобы выделить разницу. Это означает, что вы не будете следовать подходу "имитация имиджа объявлений K & R", но подход Stroustrup С++:

Мы не объявляем *bar целым числом. Мы объявляем bar как int*. Если мы хотим инициализировать вновь созданную переменную в той же строке, ясно, что мы имеем дело с bar, а не *bar. int* bar = &foo;

Недостатки:

  • Вы должны предупредить своего ученика о проблеме объявления нескольких указателей (int* foo, bar vs int *foo, *bar).
  • Вы должны подготовить их к миру обиды. Многие программисты хотят видеть звездочку рядом с именем переменной, и они будут занимать много времени, чтобы оправдать свой стиль. И многие руководства по стилю явно используют эту нотацию (стиль кодирования ядра Linux, руководство по стилю NASA C и т.д.).

Изменить: другой подход, который был предложен, заключается в том, чтобы использовать способ K & R "mimic", но без синтаксиса "сокращенного" (см. здесь). Как только вы пропустите объявление и задание в той же строке, все будет выглядеть намного более согласованным.

Однако рано или поздно ученику придется рассматривать указатели как аргументы функции. И указатели как типы возврата. И указатели на функции. Вам нужно будет объяснить разницу между int *func(); и int (*func)();. Я думаю, рано или поздно все будет разваливаться. И, возможно, скорее лучше, чем позже.

Ответ 6

Есть причина, по которой стиль K & R отличается int *p, а стиль Stroustrup отличается int* p; оба действительны (и означают одно и то же) на каждом языке, но, как сказал Страуступ:

Выбор между "int * p;" и "int * p;" не о правильном и неправильном, а о стиле и акценте. C подчеркнутые выражения; декларации часто считались не более чем необходимым злом. С другой стороны, С++ имеет большой упор на типы.

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

Поэтому некоторым людям будет легче начать с идеи, что int* - это нечто иное, чем int и оттуда.

Если кто-то быстро замаскирует способ взглянуть на него, который использует int* bar, чтобы иметь bar как вещь, которая не является int, а указателем на int, тогда они быстро заметят, что *bar делает что-то с bar, а остальное будет следовать. Как только вы это сделаете, вы можете позже объяснить, почему C-кодировщики предпочитают int *bar.

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

Ответ 7

TL;DR:

Q: Как объяснить C-указатели (декларация против унарных операторов) новичку?

A: не надо. Объясните указатели новичку и покажите им, как представить свои концепции указателей в синтаксисе C. после.


Мне недавно понравилось объяснять указатели начинающему программисту C и наткнулся на следующую трудность.

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

Поэтому: начните с объяснения указателей и убедитесь, что они действительно понимают их:

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

  • Объясните с псевдокодом: просто напишите адрес foo и значение, сохраненное в строке.

  • Затем, когда ваш новичок понимает, что такое указатели, и почему и как их использовать; затем отобразите отображение на синтаксис C.

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

Ответ 8

Эта проблема несколько запутанна, когда вы начинаете изучать C.

Вот основные принципы, которые помогут вам начать работу:

  • В C есть только несколько базовых типов:

    • char: целочисленное значение с размером 1 байт.

    • short: целочисленное значение с размером 2 байта.

    • long: целочисленное значение с размером 4 байта.

    • long long: целочисленное значение с размером 8 байтов.

    • float: нецелое значение с размером 4 байта.

    • double: нецелое значение с размером 8 байтов.

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

    Целые типы short, long и long long обычно сопровождаются int.

    Это не обязательно, и вы можете использовать их без int.

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

    Итак, суммируем это:

    • short совпадает с short int, но не обязательно совпадает с int.

    • long совпадает с long int, но не обязательно совпадает с int.

    • long long совпадает с long long int, но не обязательно совпадает с int.

    • В данном компиляторе int есть либо short int, либо long int или long long int.

  • Если вы объявляете переменную какого-либо типа, вы также можете объявить другую переменную, указывающую на нее.

    Например:

    int a;

    int* b = &a;

    Итак, по сути, для каждого базового типа мы также имеем соответствующий тип указателя.

    Например: short и short*.

    Есть два способа "взглянуть на" переменную b (что, вероятно, смущает большинство новичков):

    • Вы можете рассматривать b как переменную типа int*.

    • Вы можете рассматривать *b как переменную типа int.

    Следовательно, некоторые люди объявят int* b, тогда как другие объявят int *b.

    Но дело в том, что эти два объявления идентичны (пробелы бессмысленны).

    Вы можете использовать либо b как указатель на целочисленное значение, либо *b как фактическое целочисленное значение.

    Вы можете получить (прочитать) указанное значение: int c = *b.

    И вы можете установить (записать) указанное значение: *b = 5.

  • Указатель может указывать на любой адрес памяти, а не только на адрес некоторой переменной, которую вы ранее объявили. Однако вы должны быть осторожны при использовании указателей, чтобы получить или установить значение, расположенное по указанному адресу памяти.

    Например:

    int* a = (int*)0x8000000;

    Здесь мы имеем переменную a, указывающую на адрес памяти 0x8000000.

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

    Вы можете безопасно изменить значение a, но вы должны очень осторожно изменить значение *a.

  • Тип void* является исключительным в том, что он не имеет соответствующего "типа значения", который может быть использован (т.е. вы не можете объявить void a). Этот тип используется только как общий указатель на адрес памяти, без указания типа данных, которые находятся в этом адресе.

Ответ 9

Возможно, преодоление этого чуть более упростит:

#include <stdio.h>

int main()
{
    int foo = 1;
    int *bar = &foo;
    printf("%i\n", foo);
    printf("%p\n", &foo);
    printf("%p\n", (void *)&foo);
    printf("%p\n", &bar);
    printf("%p\n", bar);
    printf("%i\n", *bar);
    return 0;
}

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

По моему опыту, он преодолел это: "Почему это печатает таким образом?" hump, а затем сразу же показывает, почему это полезно в функциональных параметрах путем практического использования (в качестве прелюдии к некоторому базовому материалу K & R, например синтаксическому разбору/обработке массива), что делает урок не просто понятным, но и ручным.

Следующий шаг - заставить их объяснить вам, как i[0] относится к &i. Если они могут это сделать, они не забудут этого, и вы можете начать говорить о структурах, даже немного досрочно, просто так, чтобы он погрузился.

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

Ответ 10

Тип выражения *bar равен int; таким образом, тип переменной (и выражения) bar равен int *. Поскольку переменная имеет тип указателя, ее инициализатор также должен иметь тип указателя.

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

Ответ 11

int *bar = &foo;

Question 1: Что такое bar?

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

Question 2: Что такое &foo?

Ans: foo - это переменная типа int, которая хранится в некотором допустимом месте памяти, и это местоположение мы получаем от оператора &, поэтому теперь у нас есть некоторое допустимое расположение памяти &foo.

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

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

Ответ 12

Я бы скорее прочитал его, поскольку первый * применим к int больше, чем bar.

int  foo = 1;           // foo is an integer (int) with the value 1
int* bar = &foo;        // bar is a pointer on an integer (int*). it points on foo. 
                        // bar value is foo address
                        // *bar value is foo value = 1

printf("%p\n", &foo);   // print the address of foo
printf("%p\n", bar);    // print the address of foo
printf("%i\n", foo);    // print foo value
printf("%i\n", *bar);   // print foo value

Ответ 13

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

Ответ 14

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

Я бы написал (не только для новичка, но и для себя):   int * bar = & foo; вместо   int * bar = & foo;

это должно сделать очевидным, что такое связь между синтаксисом и семантикой

Ответ 15

Уже отмечалось, что * имеет несколько ролей.

Есть еще одна простая идея, которая может помочь новичкам понять вещи:

Подумайте, что "=" имеет несколько ролей.

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

Когда вы видите:

int *bar = &foo;

Подумайте, что это почти эквивалентно:

int *bar(&foo);

Скобки принимают предварительную оценку по звездочке, поэтому "& foo" гораздо легче интуитивно приписывается "бару", а не "* бар".

Ответ 16

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

template<typename T>
using ptr = T*;

Затем это можно использовать как

ptr<int> bar = &foo;

После этого сравните синтаксис normal/C с этим подходом только С++. Это также полезно для объяснения указателей const.

Ответ 17

Источником путаницы является тот факт, что символ * может иметь разные значения в C, в зависимости от того, в каком виде он используется. Чтобы объяснить указатель на новичка, следует пояснить значение символа * в другом контексте.

В объявлении

int *bar = &foo;  

Символ * не оператор косвенности. Вместо этого он помогает указать тип bar, информирующий компилятор, что bar является указателем на int. С другой стороны, когда он появляется в заявлении, символ * (при использовании в качестве унарного оператора) выполняет косвенное обращение. Поэтому утверждение

*bar = &foo;

будет неправильным, поскольку он присваивает адрес foo объекту, на который указывает bar, а не самому bar.

Ответ 18

", возможно, записывая его как int * bar, становится более очевидным, что звезда фактически является частью типа, а не частью идентификатора." Так и я. И я говорю, что это нечто вроде типа, но только для одного имени указателя.

"Конечно, это приводит вас к разным проблемам с неинтуитивными вещами, такими как int * a, b".

Ответ 19

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

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

int x;

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

Таким образом, объявления

int *p;
int a[3];

укажите, что p является указателем на int, потому что '* p' имеет тип int и что a является массивом из ints, потому что [3] (игнорируя конкретное значение индекса, которое намечается как размер массива ) имеет тип int.

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

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

Ответ 20

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

Когда вы пишете

int *bar = &foo;

группы компилятора, которые в качестве

{ int * } bar = &foo;

То есть: вот новая переменная, ее имя bar, ее тип - указатель на int, а его начальное значение - &foo.

И вы должны добавить: = выше обозначает инициализацию, а не аффектацию, тогда как в следующих выражениях *bar = 2; она есть аффектация

Изменить за комментарий:

Остерегайтесь: в случае множественного объявления * относится только к следующей переменной:

int *bar = &foo, b = 2;

bar является указателем на int, инициализированным адресом foo, b является инициализацией int до 2, а в

int *bar=&foo, **p = &bar;

bar по-прежнему указатель на int, а p - указатель на указатель на int, инициализированный адресом или строкой.

Ответ 21

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

"char * pstr" он похож на

"char str [80]"

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

Посмотрите примеры:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv, char **env)
{
    char str[] = "This is Pointer examples!"; // if we assume str[] is located in 0x80001000 address

    char *pstr0 = str;   // or this will be using with
    // or
    char *pstr1 = &str[0];

    unsigned int straddr = (unsigned int)pstr0;

    printf("Pointer examples: pstr0 = %08x\n", pstr0);
    printf("Pointer examples: &str[0] = %08x\n", &str[0]);
    printf("Pointer examples: str = %08x\n", str);
    printf("Pointer examples: straddr = %08x\n", straddr);
    printf("Pointer examples: str[0] = %c\n", str[0]);

    return 0;
}

Результаты понравятся 0x2a6b7ed0 - адрес str []

~/work/test_c_code$ ./testptr
Pointer examples: pstr0 = 2a6b7ed0
Pointer examples: &str[0] = 2a6b7ed0
Pointer examples: str = 2a6b7ed0
Pointer examples: straddr = 2a6b7ed0
Pointer examples: str[0] = T

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

Ответ 22

Я бы объяснил, что ints - это объекты, такие как float и т.д. Указатель - это тип объекта, значение которого представляет собой адрес в памяти (следовательно, по умолчанию указатель указывает на NULL).

Когда вы сначала объявляете указатель, вы используете синтаксис типа-указателя. Он читается как "целочисленный указатель, называемый именем, который может указывать на адрес любого целочисленного объекта". Мы используем этот синтаксис во время объявления, подобно тому, как мы объявляем int как "int num1", но мы используем только "num1", когда хотим использовать эту переменную, а не "int num1".

int x = 5;//целочисленный объект со значением 5

int * ptr;//целое число со значением NULL по умолчанию

Чтобы указать указатель на адрес объекта, мы используем '&' символ, который можно считать "адресом".

ptr = & x;//now value - это адрес 'x'

Поскольку указатель является только адресом объекта, чтобы получить фактическое значение, удерживаемое по этому адресу, мы должны использовать символ "*", который при использовании перед указателем означает "значение по адресу, на которое указывает".

std:: cout < * PTR;//распечатать значение по адресу

Вы можете вкратце объяснить, что "это" оператор, который возвращает разные результаты с разными типами объектов. При использовании с указателем оператор '' больше не означает "умноженный на".

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

Ответ 23

Указатель - это просто переменная, используемая для хранения адресов.

Память на компьютере состоит из байтов (байт состоит из 8 бит), расположенных последовательно. Каждый байт имеет число, связанное с ним, как индекс или индекс в массиве, который называется адресом байта. Адрес байта начинается от 0 до единицы меньше, чем размер памяти. Например, скажем, в 64 МБ ОЗУ 64 * 2 ^ 20 = 67108864 байт. Поэтому адрес этих байтов будет начинаться от 0 до 67108863.

введите описание изображения здесь

Давайте посмотрим, что произойдет, когда вы объявите переменную.

метки int;

Как известно, int занимает 4 байта данных (при условии, что мы используем 32-разрядный компилятор), поэтому компилятор резервирует 4 последовательных байта из памяти для хранения целочисленного значения. Адрес первого байта из 4 выделенных байтов известен как адрес переменных меток. Допустим, что адрес из 4 последовательных байтов - 5004, 5005, 5006 и 5007, тогда адрес переменных меток будет 5004. введите описание изображения здесь

Объявление переменных указателя

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

Синтаксис: data_type *pointer_name;

data_type - тип указателя (также известный как базовый тип указателя). pointer_name - это имя переменной, которая может быть любым допустимым идентификатором C.

Давайте рассмотрим несколько примеров:

int *ip;

float *fp;

int * ip означает, что ip - это указательная переменная, способная указывать на переменные типа int. Другими словами, переменная-указатель ip может хранить адрес переменных только типа int. Аналогично, переменная указателя fp может хранить только адрес переменной типа float. Тип переменной (также известный как базовый тип) ip является указателем на int и тип fp является указателем на float. Переменная указателя указателя на int может быть символически представлена ​​как (int *). Точно так же указательная переменная указателя типа float может быть представлена ​​как (float *)

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

int *ip, i = 10;
float *fp, f = 12.2;

ip = &i;
fp = &f;

Источник: thecguru является самым простым, но подробным объяснением, которое я когда-либо обнаружил.