ГлавнаяСтатьиСПО и программирование → Памятка по указателям в Си

Памятка по указателям в Си

18 апреля 2013 года
Ключевые слова: Си , программирование

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

Что такое указатели.

Всякая переменная помимо значения имеет адрес. Это номер ячейки где находится ее первый байт. Общее число занимаемых переменной байт можно найти оператором sizeof

char a; 
printf( "%d\n", sizeof a ); // дает 1
printf( "%d\n", sizeof(int) ); // дает 4

Адрес можно определить оператором &
а значение переменной по этому адресу возвращает оператор разыменования *

int a=10;
printf( "%p\n", &); // получим адрес, что-то вроде  0xbfffb836c
printf( "%d\n", *(&a) ) ; //  получим значение a=10 

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

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

Действия с указателями.

1) Объявление - чтобы объявить указатель перед именем переменной ставим *.

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

char * p1; // этот указатель будет хранить адреса переменных типа char
int * p2;   // а этот типа int

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

2) Инициализация - присвоение указателю адреса какой-либо переменной.

char a=10;
char *p=&;  
printf( "%p\n",  p ); // получим адрес a

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

3) Разыменование - получение значения переменной по адресу на который ссылается указатель.

char a=10:
char * p=&a;
printf( "%d\n", *); //  получим a=10   
 

4) Присвоения и адресная арифметика.

Указатели можно присваивать. Например p2=p1 означает, что указатели получили одинаковые адреса и ссылаются на одну и ту же переменную.
В адресной арифметике указателей имеют смысл и определены лишь два действия - прибавление ( вычитание) целого числа и разность указателей.
Если p2=p1+1 то в p2 будет не адрес следующего байта памяти, а следующего первого байта переменной данного типа. Разность указателей p2-p1 дает число переменных данного типа поместившихся между p1 и p2.

int * p1=(int *) 0x1000;  // зададим фиксированный адрес
int * p2=p1+1;
printf( "%p\n",  p1 );     //  даст 0x1000
printf( "%p\n",  p2 );     // даст 0x1004
printf( "%d\n", (p2-p1) ); // помещается 1 переменная типа int  

5) Использование указателей для манипуляций с данными и байтами.

Если указатель ссылается на переменную, то ее можно изменить через него

char  a=10;
char* p=&a;
*p=2*a;
printf( "%d\n", a ); // теперь a=20
 

Для манипуляции с байтами используют приведение типа указателя. Например, пусть у нас есть переменная типа int и надо выделить два ее первых байта:

int  a=259;
char *p=(char*)&a;
printf( "%d\n", *); //  даст 3
printf( "%d\n", *(p+1) ); //  даст 1

6) Указатели и массивы.

Пусть имеется массив, для определенности массив символов char str[ ]="Linux". Как определить указатель ссылающийся на его начало? По логике, пожалуй, так: char * s = & str[ 0 ]. И этот способ действительно работает. Однако, волей разработчиков в Си принято, что имя массива одновременно является и указателем на его начало, т. е. правильно писать: char * s =str

Обратится к элементу массива можно двояко, как с помощью индексной переменной так и указателя.
Например str[ 2 ]=' n ', а str[ 6 ]=0 (признак конца строки). Аналогично *s=' L ', *(s+5)=' x ' и т. п.
Выведем на экран все символы массива:

char str[ ]="Linux";
char * s=str;
int i=0;
while ( str[i] )  printf( "%c", str[i++] ); // вывод через индексную переменную
printf("\n");
while ( *)  printf( "%c", *s++ ); // вывод через указатель

На практике строку лучше вывести при помощи спецификатора вывода %s который работает с переменными типа char* т.е. с адресами символьных массивов.

char str[ ]="Linux";
char * s=str;    
printf( "%s\n", str ); 
printf( "%s", s ); 

Заметим, что если создать указатель ссылающийся на массив то можно обращаться по указателю к хранимым значениям как к элементам массива, например:
char * str ="Unix";
str[0] => 'U' и т.д.

7) Косвенная адресация.

Косвенная адресация это когда указатель ссылается не на переменную, а на другой указатель. А тот еще куда-нибудь...
Цепочка ссылок не ограничена, но обычно их не более 2-х. Легко догадаться, что количество ссылок в цепочке опять обозначается звездочками

char * p1;
char ** p2;
char a=10;
 
p1=&a;  // p1=>a
p2=&p1; // p2=>p1=>a  
 
printf( "%p\n", *p2 ); // даст p1
printf( "%d\n", **p2) ; // даст a

Рассмотрим задачу. Geany всегда создает образцовый Си файл вида:

#include <stdio.h>
 
int main(int argc, char **argv)
{      
  return 0;
}

Что это означает?
В функцию main передают два аргумента, связанные с командной строкой запуска исполняемой программы.
argc - argument counter - счетчик числа слов в командной строке
argv - вероятно argument verbal - фразы аргумента
Тип char * ссылался бы на массив символов т.е. на слово, значит char ** ссылается на массив слов и к ним можно обращаться как к элементам массива. Проиллюстрируем это программкой test.c:

//  test.c
#include <stdio.h>
 
int main(int argc, char **argv)
{
    printf( "%d", argc );    
    printf( "\n %s", argv[0] );  // можно обратится как к массиву  
    printf( "\n %s", *(argv+1) ); // а можно разыменовать указатель
    printf( "\n" );
 
    return 0;
}

При вызове ее командой ./test opt1 она вернет число слов в командной строке и первые два слова команды:
2
./test
opt1

Список умных книг

  1. Г. Шилдт. Полный справочник по С++, 4-е издание.: Пер. с англ. -М.: Издательский дом "Вильямс", 2010.- 800с / Cтр.118-134
  2. Ликбез по переменным и указателям в С++

Комментарии

#1. 31 марта 2016 года, 13:39. Stix пишет:
Наилучшая справка по разыменованию и вообще по указателям в C! Автору большая благодарность!
#2. 2 апреля 2016 года, 15:58. Андрей пишет:
Спасибо!

Оставьте свой комментарий

Ваше имя:

Комментарий:

Формулы на латехе: $$f(x) = x^2-\sqrt{x}$$ превратится в $$f(x) = x^2-\sqrt{x}$$.
Для выделения используйте следующий код: [i]курсив[/i], [b]жирный[/b].
Цитату оформляйте так: [q = имя автора]цитата[/q] или [q]еще цитата[/q].
Ссылку начните с http://. Других команд или HTML-тегов здесь нет.

Сколько будет 38+5?