Как подходить к указателям в C?

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

int doSomething(char **hihi);

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

arr = &arr[0];

Это все, что я знаю о указателях, и мне интересно, как я могу углубить свое видение по указателям. Я обыскал сеть, и я не мог найти полезных подсказок для подсказок. И я также хочу знать, почему они так важны и есть ли способ понять, что происходит, не используя printf() для печати своих адресов (p) и значений (\*p)??

Ответ 1

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

Если вы знакомы с "ярлыками" в Windows или софт-ссылками в файловых системах linux, тогда он может помочь, как вы начинаете, думать о указателе как ярлыке (softlink) для другого объекта ( является ли этот объект структурой, встроенным типом, другим указателем и т.д.).

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

Одно из отличий заключается в том, что дважды щелкните ярлык, он ведет себя так, как если бы вы дважды щелкнули то, на что указывают. Это не так с указателями - вы всегда должны явно разыменовывать указатель с помощью "*" или "- > ", чтобы получить доступ к тому, на что он указывает. Другое различие заключается в том, что довольно распространено иметь указатели на указатели на что-то в C.

Что касается жаргона, вам просто нужно усвоить его, к сожалению. "int doSomething (char ** hihi)" означает "функция, называемая doSomething, которая возвращает целое число и принимает в качестве параметра указатель на указатель a char". Решающим моментом является то, что "char ** hihi" означает "указатель на указатель на char. Мы будем называть указатель на указатель на char hihi". Вы говорите, что "тип" hihi char **, и что "тип" * hihi (что вы получаете, когда вы разыскиваете указатель) составляет char *, а тип ** hihi - это char.

Часто в C указатель на char означает строку (другими словами, это указатель на первый char в NUL-концевом массиве). Так часто "char *" означает "строка", но это не обязательно. Это может означать только указатель на один char. Немного похоже на ярлык для 1-байтового файла в Windows (ну, в любом случае, с FAT32) указатель на char в C на самом деле больше, чем тот, на который он указывает: -)

Аналогично, char ** часто означает не просто указатель на один указатель на строку, а на массив указателей строк. Возможно, это не так, но если это произойдет, то следующая небольшая картинка может помочь:

hihi
 ____            ____                     ________     _________      _______
|____|   -----> |____|  *hihi       ---> |___A____|   |___B_____|    |___C___|
                |____|  *(hihi+1)   ------------------^              ^
                |____|  *(hihi+2)   ---------------------------------|
                | ...|    etc.

hihi указывает на усилие блока башни, что является моим способом представления массива указателей. Как вы уже отметили, я мог бы написать hihi [0] вместо * hihi, hihi [1] вместо * (hihi + 1) и т.д.

Это непрерывный блок памяти, и каждый его кусок указателя содержит адрес (то есть он "указывает на" ) еще один блок памяти, с сайта goodness-know-where, содержащий один или несколько символов, Итак, hihi [0] является адресом первой char строки A, hihi [1] является адресом первой char строки B.

Если hihi не указывает на массив, только один указатель, то блок башни - это бунгало. Аналогично, если * hihi не указывает на строку, только один char, то длинный тонкий блок является квадратом. Вы могли бы спросить: "Откуда я знаю, сколько этажей имеет блок башни?". Это большое дело в программировании на языке С - обычно либо документация по функциям скажет вам (это может сказать "1", либо "12", или "достаточно для того, что вы говорите мне делать", либо вы передадите количество этажей в качестве дополнительного параметра, иначе документация сообщит вам, что массив "NULL terminated", что означает, что он будет продолжать чтение, пока не увидит адрес/значение NULL, а затем остановится. Основная функция фактически выполняет как второе и третье - argc содержит количество аргументов, и просто чтобы быть в безопасности, argv завершает NULL.

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

Ответ 2

Я думаю, что именно здесь классические книги более полезны, чем большинство онлайн-ресурсов. Если вы можете получить копию, очень внимательно прочитайте язык программирования C (a.k.a K & R). Если вы хотите узнать больше, перейдите на Expert C Programming: Deep Secrets (просто Google).

Ответ 3

Указатель - это место.

Массив является последовательной группой мест.

Всегда есть значение в месте. (Это может быть оставшийся барахло).

Каждая переменная имеет место.

Для переменных указателя значение на его месте является местом.

Это как охота за сокровищами. "Посмотрите в почтовый ящик 13 для заметки, в которой указывается, какой почтовый ящик содержит вашу поздравительную открытку".

И если почтовый ящик 13 содержит заметку, которая читает "13", ваша поздравительная открытка будет долгое время! (Это ошибка, вызванная ссылкой на круговой указатель.; -)

Ответ 4

При чтении K & R может быть лучшим выбором здесь, я попытаюсь сделать это немного яснее:

Указатель сам по себе является переменной. Но вместо сохранения значения он сохраняет только адрес. Подумайте об этом как о индексе: как в вашей адресной книге указатель на то, что вы ищете (например, номер телефона на какое-то имя), он указывает, где хранится информация. В вашей адресной книге он может сказать: "Посмотрите на стр. 23, чтобы найти номер телефона Джо". В случае указателя он просто говорит: "Посмотрите на адрес памяти 1234, чтобы получить информацию, на которую я указываю". Поскольку значение указателя само по себе является только адресом памяти, вы можете сделать с ним арифметику - например, добавление значений (это будет то же самое, что доступ к элементам массива: если указатель указывает на массив, адрес, следующий за указателем указывает на доступ к следующему элементу массива). Ваш пример функции int doSomething(char *hihi) будет содержать hihi, указывающий на адрес памяти, который вы передали ему при вызове. Это полезно, если вы хотите передавать большие объемы данных - вместо копирования данных (что происходит в функции типа void blah(int a) со значением a) вы копируете только ее местоположение.

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

Ответ 5

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

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

Здесь вы можете найти учебное пособие (действительно подробное объяснение). Прямая ссылка PDF.

Ответ 6

/* Given a string of characters like this one: */
char *string = "Hello!\n";

/* Memory will contain something like:
0x00100 'H'
0x00101 'e'
0x00102 'l'
0x00103 'l'
0x00104 'o'
0x00105 '!'
0x00106 '\n'
0x00107 '\0'
*/

/* And the program code will say: */
string=0x00100;

/* C doesn't really have arrays */
char c=string[3];
/* is just syntactic sugar for: */

char c=*((char*)((void*)string + 3 * sizeof(char)));
/* ie. 0x00100 + 3 * 1 */
/* ie. 0x00103 */
/* and * dereferences 0x00103, this means char_in(0x00103) */

/* When you pass a pointer you are actually passing the value
   of the memory position */

int a;          /* allocates space for a random four byte value in
                   0x00108 */
scanf("%d",&a); /* &a = 0x00108 scanf now knows that it has to store
                  the value in 0x0108 */

/* Even if you declare: */
int b[23];
/* b will be just a pointer to the beginning of 23 allocated ints.
   ie. 0x0010C */

/* pointer arithmetic is different from normal types in that: */
b++;
/* is actually: */
b+=4; /* b+=1*sizeof(int); */
/* So pointers in C work more like arrays */

Ответ 7

Давос, ты в колледже? Вы главный специалист по информационным технологиям? Одна вещь, которую вы можете рассмотреть, - это взять класс ассемблерного языка (MIPS, x86). Я был главным инженером в области электротехники, и мне нужно было брать такие классы низкого уровня. Одна вещь, которую я наблюдал, заключалась в том, что ясное понимание языка ассемблера действительно помогло мне, когда я начал изучать C++. В частности, это дало мне гораздо более четкое понимание указателей.

Указатели и разыменование являются фундаментальными понятиями на уровне языка ассемблера. Если вы впервые изучаете указатели, я нахожу, что в некотором смысле "С" скрывает его немного, и на самом деле он становится яснее на уровне ассемблера. Затем вы можете получить это знание более низкого уровня и посмотреть, как язык "C" просто накладывает на него некоторый синтаксис.

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

Ответ 8

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

Ответ 9

Если вы действительно хотите понять указатели, Вам нужна хорошая лекция в колледже.

Лучший из тех, что я когда-либо видел, - этот.

Ничего другого не сравнивается.

Также ознакомьтесь с лекцией по Тьюрингу. Это очень хорошо сделано.

Ответ 10

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

В 32-разрядной операционной системе целое число является 4-байтным числом и должно находиться в диапазоне

0 < значение указателя < (2 ^^ 32) -1

В 64-битной операционной системе целое число является 8-байтовым числом и должно находиться в диапазоне

0 < значение указателя < (2 ^^ 64) -1

Значение указателя = 0 интерпретируется как специальное значение флага с именем NULL, которое указывает, что этот указатель не указывает на полезную переменную.

Указатели используются для косвенности. Некоторые регистры в ЦП действуют как указатели, например. счетчик программ (ПК) и регистры адресов.

Ответ 11

Вы читали K & R? Если вы этого не сделали, я бы сказал, что вам следует начать.

Ответ 13

Несколько нетрадиционное предложение: http://www.youtube.com/watch?v=Rxvv9krECNw

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