Пара слов про указатели в Си.
При изучении Си у начинающих часто возникают вопросы связанные с указателями, думаю вопросы у всех возникают примерно одинаковые поэтому опишу те, которые возникли у меня.
Для чего нужен указатель?
Почему всегда пишут “указатель типа” и чем указатель типа uint16_t отличается от указателя типа uint8_t?
И кто вообще выдумал указатель?
Перед тем как ответить на эти вопросы, давайте вспомним, что такое указатель.
Указатель — это переменная, которая содержит адрес некоторого элемента данных(переменной, константы, функции, структуры).
Для объявления переменной как указателя необходимо перед её именем поставить *, а для получения адреса переменной используется &(унарный оператор взятия адреса).
В данном случае в р содержится адрес переменной а. Но что интересно, для дальнейшей работы с указателем не надо писать звёздочку, она нужна только при объявлении.
В данном случае в р содержится адрес переменной b, но если мы хотим получить значение лежащее по этому адресу, то нужно использовать оператор разыменования, та же звёздочка *.
Таким образом, переменная new_simbol будет содержать ascii код символа 'a'.
Теперь перейдём непосредственно к вопросам, для чего нужен указатель. Представьте что у нас есть массив, с которым мы хотим работать в функции. Для того чтобы передать массив в функцию его надо скопировать, то есть потратить память, которой у МК и так мало, поэтому более правильным решение будет не копировать массив, а передать адрес его первого элемента и размер.
Можно это сделать так
или так
Поскольку имя массива, содержит адрес его первого элемента, это есть не что иное, как указатель. Перемещаться по массиву можно с помощью простейших арифметических операций, например, для того чтобы получить значение пятого элемента массива, необходимо к адресу массива(адрес первого элемента) прибавить 4 и применить оператор разыменования.
И тут же возникает вопрос, для чего везде пишут тип перед указателем? Все просто, передавая адрес первого элемента массива и размер массива, мы говорим: Вот отсюда(указатель) выкопать 10 ямок(размер массива), приходим через два часа, а те, кто должны были выкопать ямки, вызвали трактор и роют котлован. Чтобы не попасть в такую ситуацию надо было указать размер ямки, в нашей аналогии тип указателя так, как тип определяет сколько байт будет занимать переменная в памяти.
Таким образом, указывая тип указателя, мы говорим компилятору, вот тебе адрес начала массива, один элемент массива занимает 2 байта, таких элементов в массиве 10 итого сколько памяти выделить под этот массив? 20 байт — отвечает компилятор. Для наглядности давайте возьмем указатель типа void, для него не определено сколько места он занимает — это просто адрес, приведём его к указателям разного типа и выполним операцию разадресации.
Также в функцию можно передать и указатель на структуру. Так как разметка структуры известна, нам достаточно передать только адрес её начала, а компилятор сам разобьёт её на поля.
Ну и последний вопрос, кто выдумалэту бяку указатель. Для того чтобы разобраться в этом вопросе, надо обратиться к ассемблеру, например AVR, и там мы найдём инструкции
Становится понятно, что Х содержит указатель(адрес) и, оказывается, нет никакого злого дядьки, который придумал указатель, чтобы запудрить всем мозги, работа с указателями(адресами) поддерживается на уровне ядра МК.
Для чего нужен указатель?
Почему всегда пишут “указатель типа” и чем указатель типа 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, для него не определено сколько места он занимает — это просто адрес, приведём его к указателям разного типа и выполним операцию разадресации.
Также в функцию можно передать и указатель на структуру. Так как разметка структуры известна, нам достаточно передать только адрес её начала, а компилятор сам разобьёт её на поля.
Ну и последний вопрос, кто выдумал
st X, r1 ;сохранить содержимое r1 в SRAM по адресу Х, где X – пара регистров r26, r27
ld r1,X ; загрузить в r1 содержимое SRAM по адресу Х, где X – пара регистров r26, r27
Становится понятно, что Х содержит указатель(адрес) и, оказывается, нет никакого злого дядьки, который придумал указатель, чтобы запудрить всем мозги, работа с указателями(адресами) поддерживается на уровне ядра МК.
Похожие статьи