Пара слов про указатели в Си.

Пара слов про указатели в Си.

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

Для чего нужен указатель?

Почему всегда пишут “указатель типа” и чем указатель типа uint16_t отличается от указателя типа uint8_t?

И кто вообще выдумал указатель?

Перед тем как ответить на эти вопросы, давайте вспомним, что такое указатель.
Указатель — это переменная, которая содержит адрес некоторого элемента данных(переменной, константы, функции, структуры).
Пара слов про указатели в Си.


Для объявления переменной как указателя необходимо перед её именем поставить *, а для получения адреса переменной используется &(унарный оператор взятия адреса).

char a = 'a';

char *p = &a;

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

char a = 'a';

char b = 'b';

char *p = &a;

p = &b;

В данном случае в р содержится адрес переменной b, но если мы хотим получить значение лежащее по этому адресу, то нужно использовать оператор разыменования, та же звёздочка *.

char new_simbol = 0;

char a = 'a';

char *p = &a;

new_simbol = *p;

Таким образом, переменная new_simbol будет содержать ascii код символа 'a'.

Теперь перейдём непосредственно к вопросам, для чего нужен указатель. Представьте что у нас есть массив, с которым мы хотим работать в функции. Для того чтобы передать массив в функцию его надо скопировать, то есть потратить память, которой у МК и так мало, поэтому более правильным решение будет не копировать массив, а передать адрес его первого элемента и размер.
m[100] ={1,2,3...};

Можно это сделать так
void foo(char *m, uint8_t size)
{

}

или так
void foo(char m[], uint8_t size)
{

}

Поскольку имя массива, содержит адрес его первого элемента, это есть не что иное, как указатель. Перемещаться по массиву можно с помощью простейших арифметических операций, например, для того чтобы получить значение пятого элемента массива, необходимо к адресу массива(адрес первого элемента) прибавить 4 и применить оператор разыменования.
m[4] = *(m + 4);


И тут же возникает вопрос, для чего везде пишут тип перед указателем? Все просто, передавая адрес первого элемента массива и размер массива, мы говорим: Вот отсюда(указатель) выкопать 10 ямок(размер массива), приходим через два часа, а те, кто должны были выкопать ямки, вызвали трактор и роют котлован. Чтобы не попасть в такую ситуацию надо было указать размер ямки, в нашей аналогии тип указателя так, как тип определяет сколько байт будет занимать переменная в памяти.

Таким образом, указывая тип указателя, мы говорим компилятору, вот тебе адрес начала массива, один элемент массива занимает 2 байта, таких элементов в массиве 10 итого сколько памяти выделить под этот массив? 20 байт — отвечает компилятор. Для наглядности давайте возьмем указатель типа void, для него не определено сколько места он занимает — это просто адрес, приведём его к указателям разного типа и выполним операцию разадресации.
Пара слов про указатели в Си.

Также в функцию можно передать и указатель на структуру. Так как разметка структуры известна, нам достаточно передать только адрес её начала, а компилятор сам разобьёт её на поля.

Ну и последний вопрос, кто выдумал эту бяку указатель. Для того чтобы разобраться в этом вопросе, надо обратиться к ассемблеру, например AVR, и там мы найдём инструкции

st X, r1 ;сохранить содержимое r1 в SRAM по адресу Х, где X – пара регистров r26, r27

ld r1,X ; загрузить в r1 содержимое SRAM по адресу Х, где X – пара регистров r26, r27

Становится понятно, что Х содержит указатель(адрес) и, оказывается, нет никакого злого дядьки, который придумал указатель, чтобы запудрить всем мозги, работа с указателями(адресами) поддерживается на уровне ядра МК.
комментарии
2