Работа с указателями язык с

Указатель в языке Си

Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.

Указатели широко используются в программировании на языке Си.
Указатели часто используются при работе с массивами.

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

Каждая переменная в памяти имеет свой адрес — номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).

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

Тип указателя — это тип переменной, адрес которой он содержит.

Для работы с указателями в Си определены две операции:


Для указанного примера обращение к одним и тем же значениям переменной и адреса представлено в таблице

Расположение в памяти переменной a и указателя b:

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

Комментариев к записи: 77

// Функция кодирования текста

uint32_t* encrypt(uint32_t* v, uint32_t* k)
<
uint32_t v0 = v[0];
uint32_t v1 = v[1];
uint32_t sum = 0;

/* a key schedule constant */
uint32_t delta = 0x9e3779b9;

/* cache key */
uint32_t k0 = k[0];
uint32_t k1 = k[1];
uint32_t k2 = k[2];
uint32_t k3 = k[3];
uint32_t i;

/* basic cycle start */
for (i = 0; i 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
>
/* end cycle */

return v; // Возвращаем указатель на нулевой элемент массива зашифрованного числа

// Функция декодирования текста

uint32_t* decrypt(uint32_t* v, uint32_t* k)
<

/* set up */
uint32_t v0 = v[0];
uint32_t v1 = v[1];
uint32_t sum = 0xC6EF3720;
uint32_t i;

/* a key schedule constant */
uint32_t delta = 0x9e3779b9;

/* cache key */
uint32_t k0 = k[0];
uint32_t k1 = k[1];
uint32_t k2 = k[2];
uint32_t k3 = k[3];

uint32_t* plain;
char shelf1[8]; // В массив записан текст из 8-символов
char shelf2[8];

plain = (uint32_t*)shelf1; // Загружаем текст в plain
uint32_t* encoded = encrypt(plain, key); // Шифруем текст

uint32_t* decoded = decrypt(plain, key); // Расшифровываем текст

uint32_t* decrypt(uint32_t* v, uint32_t* k)
<

/* set up */
uint32_t v0 = v[0];
uint32_t v1 = v[1];

%ls pointers.c:14:66: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int *’ [-Wformat=] printf(«\n Значение указателя b равно %x шестн.», b);

^ %ls pointers.c:15:85: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int **’ [-Wformat=] ntf(«\n Адрес расположения указателя b равен %x шестн.», &b);

#include
#include
#include

void helloWorld (GtkWidget *wid, GtkWidget *win)
<
GtkWidget *dialog = NULL ;

dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, sqlite3_libversion());
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
>

int main ( int argc, char *argv[])
<
GtkWidget *button = NULL ;
GtkWidget *win = NULL ;
GtkWidget *vbox = NULL ;

/* Create a vertical box with buttons */
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_add (GTK_CONTAINER (win), vbox);

/* Enter the main loop */
gtk_widget_show_all (win);
gtk_main ();
return 0;
>

Источник

Указатели

Указатели

Э то, пожалуй, самая сложная и самая важная тема во всём курсе. Без понимания указателей дальнейшее изучении си будет бессмысленным. Указатели – очень простая концепция, очень логичная, но требующая внимания к деталям.

Определение

У казатель – это переменная, которая хранит адрес области памяти. Указатель, как и переменная, имеет тип. Синтаксис объявления указателей

Например
float *a;
long long *b;
Два основных оператора для работы с указателями – это оператор & взятия адреса, и оператор * разыменования. Рассмотрим простой пример.

Рассмотрим код внимательно, ещё раз

Была объявлена переменная с именем A. Она располагается по какому-то адресу в памяти. По этому адресу хранится значение 100.

Создали указатель типа int.

Теперь переменная p хранит адрес переменной A. Используя оператор * мы получаем доступ до содержимого переменной A.
Чтобы изменить содержимое, пишем

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

Будет выведено
4
4
8
4
Несмотря на то, что переменные имеют разный тип и размер, указатели на них имеют один размер. Действительно, если указатели хранят адреса, то они должны быть целочисленного типа. Так и есть, указатель сам по себе хранится в переменной типа size_t (а также ptrdiff_t), это тип, который ведёт себя как целочисленный, однако его размер зависит от разрядности системы. В большинстве случаев разницы между ними нет. Зачем тогда указателю нужен тип?

Арифметика указателей

В о-первых, указателю нужен тип для того, чтобы корректно работала операция разыменования (получения содержимого по адресу). Если указатель хранит адрес переменной, необходимо знать, сколько байт нужно взять, начиная от этого адреса, чтобы получить всю переменную.
Во-вторых, указатели поддерживают арифметические операции. Для их выполнения необходимо знать размер.
операция + N сдвигает указатель вперёд на N*sizeof(тип) байт.
Например, если указатель int *p; хранит адрес CC02, то после p += 10; он будет хранить адрес СС02 + sizeof(int)*10 = CC02 + 28 = CC2A (Все операции выполняются в шестнадцатиричном формате). Пусть мы создали указатель на начало массива. После этого мы можем «двигаться» по этому массиву, получая доступ до отдельных элементов.

Читайте также:  Тунец на итальянском языке

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

Массив, по сути, сам является указателем, поэтому не нужно использовать оператор &. Мы можем переписать пример по-другому

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

Указатель на указатель

У казатель хранит адрес области памяти. Можно создать указатель на указатель, тогда он будет хранить адрес указателя и сможет обращаться к его содержимому. Указатель на указатель определяется как

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

Указатели и приведение типов

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

В этом примере мы пользуемся тем, что размер типа int равен 4 байта, а char 1 байт. За счёт этого, получив адрес первого байта, можно пройти по остальным байтам числа и вывести их содержимое.

У казатель до инициализации хранит мусор, как и любая другая переменная. Но в то же время, этот «мусор» вполне может оказаться валидным адресом. Пусть, к примеру, у нас есть указатель. Каким образом узнать, инициализирован он или нет? В общем случае никак. Для решения этой проблемы был введён макрос NULL библиотеки stdlib.
Принято при определении указателя, если он не инициализируется конкретным значением, делать его равным NULL.

По стандарту гарантировано, что в этом случае указатель равен NULL, и равен нулю, и может быть использован как булево значение false. Хотя в зависимости от реализации NULL может и не быть равным 0 (в смысле, не равен нулю в побитовом представлении, как например, int или float).
Это значит, что в данном случае

вполне корректная операция, а в случае

поведение не определено. То есть указатель можно сравнивать с нулём, или с NULL, но нельзя NULL сравнивать с переменной целого типа или типа с плавающей точкой.

Примеры

Теперь несколько примеров работы с указателями
1. Пройдём по массиву и найдём все чётные элементы.

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

3. Более интересный пример. Так как размер типа char всегда равен 1 байт, то с его помощью можно реализовать операцию swap – обмена местами содержимого двух переменных.

В этом примере можно поменять тип переменных a и b на double или любой другой (с соответствующим изменением вывода и вызова sizeof), всё равно мы будет обменивать местами байты двух переменных.

4. Найдём длину строки, введённой пользователем, используя указатель

Источник

Урок №80. Указатели

Обновл. 21 Фев 2021 |

На уроке №10 мы узнали, что переменная — это название кусочка памяти, который содержит значение.

Оператор адреса &

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

При выполнении этого стейтмента процессором, выделяется часть оперативной памяти. В качестве примера предположим, что переменной b присваивается ячейка памяти под номером 150. Всякий раз, когда программа встречает переменную b в выражении или в стейтменте, она понимает, что для того, чтобы получить значение — ей нужно заглянуть в ячейку памяти под номером 150.

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

Оператор адреса & позволяет узнать, какой адрес памяти присвоен определенной переменной. Всё довольно просто:

Результат на моем компьютере:

Примечание: Хотя оператор адреса выглядит так же, как оператор побитового И, отличить их можно по тому, что оператор адреса является унарным оператором, а оператор побитового И — бинарным оператором.

Оператор разыменования *

Оператор разыменования * позволяет получить значение по указанному адресу:

Результат на моем компьютере:

Примечание: Хотя оператор разыменования выглядит так же, как и оператор умножения, отличить их можно по тому, что оператор разыменования — унарный, а оператор умножения — бинарный.

Указатели

Теперь, когда мы уже знаем об операторах адреса и разыменования, мы можем поговорить об указателях.

Указатель — это переменная, значением которой является адрес ячейки памяти. Указатели объявляются точно так же, как и обычные переменные, только со звёздочкой между типом данных и идентификатором:

Синтаксически язык C++ принимает объявление указателя, когда звёздочка находится рядом с типом данных, с идентификатором или даже посередине. Обратите внимание, эта звёздочка не является оператором разыменования. Это всего лишь часть синтаксиса объявления указателя.

Однако, при объявлении нескольких указателей, звёздочка должна находиться возле каждого идентификатора. Это легко забыть, если вы привыкли указывать звёздочку возле типа данных, а не возле имени переменной. Например:

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

Читайте также:  Учить через интернет английский язык

Присваивание значений указателю

Поскольку указатели содержат только адреса, то при присваивании указателю значения — это значение должно быть адресом. Для получения адреса переменной используется оператор адреса:

Приведенное выше можно проиллюстрировать следующим образом:

Еще очень часто можно увидеть следующее:

Результат на моем компьютере:

Тип указателя должен соответствовать типу переменной, на которую он указывает:

Следующее не является допустимым:

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

Язык C++ также не позволит вам напрямую присваивать адреса памяти указателю:

Оператор адреса возвращает указатель

Стоит отметить, что оператор адреса & не возвращает адрес своего операнда в качестве литерала. Вместо этого он возвращает указатель, содержащий адрес операнда, тип которого получен из аргумента (например, адрес переменной типа int передается как адрес указателя на значение типа int):

Результат выполнения программы:

Разыменование указателей

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

0034FD90
5
0034FD90
5

Вот почему указатели должны иметь тип данных. Без типа указатель не знал бы, как интерпретировать содержимое, на которое он указывает (при разыменовании). Также, поэтому и должны совпадать тип указателя с типом переменной. Если они не совпадают, то указатель при разыменовании может неправильно интерпретировать биты (например, вместо типа double использовать тип int).

Одному указателю можно присваивать разные значения:

Когда адрес значения переменной присвоен указателю, то выполняется следующее:

ptr — это то же самое, что и &value ;

Разыменование некорректных указателей

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

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

Следующая программа хорошо иллюстрирует вышесказанное. При запуске вы получите сбой (попробуйте, ничего страшного с вашим компьютером не произойдет):

Размер указателей

Размер указателя зависит от архитектуры, на которой скомпилирован исполняемый файл: 32-битный исполняемый файл использует 32-битные адреса памяти. Следовательно, указатель на 32-битном устройстве занимает 32 бита (4 байта). С 64-битным исполняемым файлом указатель будет занимать 64 бита (8 байт). И это вне зависимости от того, на что указывает указатель:

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

В чём польза указателей?

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

Однако, оказывается, указатели полезны в следующих случаях:

Случай №1: Массивы реализованы с помощью указателей. Указатели могут использоваться для итерации по массиву.

Случай №2: Они являются единственным способом динамического выделения памяти в C++. Это, безусловно, самый распространенный вариант использования указателей.

Случай №3: Они могут использоваться для передачи большого количества данных в функцию без копирования этих данных.

Случай №4: Они могут использоваться для передачи одной функции в качестве параметра другой функции.

Случай №5: Они используются для достижения полиморфизма при работе с наследованием.

Случай №6: Они могут использоваться для представления одной структуры/класса в другой структуре/классе, формируя, таким образом, целые цепочки.

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

Заключение

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

Совет: При объявлении указателя указывайте звёздочку возле имени переменной.

Задание №1

Какие значения мы получим в результате выполнения следующей программы (предположим, что это 32-битное устройство, и тип short занимает 2 байта):

Источник

Указатели в языке Си

Тема указателей довольно сложна и мне часто присылают вопросы по ней. В этой статье мы подробно разберем тему указателей.

Рекомендуемая работа с памятью

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

Рекомендуемый способ — это прямо описать ту область памяти, которую вы хотите использовать.

В этом примере мы задали целое число, массив и вещественное число. То есть мы явно дали имя элементу данных.

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

Почему рекомендуется делать именно так? Три причины:

Причина 1. Соблюдение принципа изоляции кода

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

Причина 2. Простой вызов в отладчике

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

Причина 3. Самодокументируемый код

Если мы дадим объектам данных осмысленные имена, то код будет хорошо читаться. Например:

Читайте также:  Узнать свой уровень корейского языка

Но так как язык Си универсален и есть много способов писать программы, то есть и такой способ как указатели.

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

Указатель — это переменная, которая содержит адрес некоторого объекта в памяти.

Для работы с указателями используются два оператора:
& — получить адрес переменной (&x — адрес переменной x)

* — получить значение переменной по адресу (*px — значение по адресу px)

Рассмотрим участок памяти. Предположим, что по адресу 54100 размещена символьная переменная char x;

При заведения указателя мы сразу говорим компилятору, что мы завели указатель на объект типа char. Чтобы не было путаницы в именах рекомендуется указатель начинать с символа «p».

Важный момент. Когда комплятор выделяет память под «char x», то выделяется один байт, потому что x — это символ, то есть это однобайтовая переменная. Но когда компилятор выделяет память под «char *px», то выделяется обычно 4 байта, так как адрес (в 32-х битовой системе) занимает 4 байта.

*px — читается как «взять значение по адресу, xранящемуся в px»

Теперь нам нужно записать:

Для этого мы пишем следующие строки:

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

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

Предположим, что px — это висячий указатель. Действия с самим указателем px могут быть любыми, программа не пострадает. Но если мы выполним действие *px над памятью, которая не была выделена программе, то операционная система прекратит действие программы и напишет что-то вроде: «Программа выполнила недопустимую операцию и будет закрыта».

Преимущество указателей

Преимущество указателей в том, что они позволяют передать в функцию значение по ссылке. Слово «ссылка» означает, что мы не передаем значение, а ссылаемся на адрес этого значения. В этом случае можно внутри функции изменять значение элемента данных. Хотя указатель и будет уничтожен после выхода из функции, но мы изменили значение памяти по указателю и это изменение значения сохранится после выход из функции.

Именно поэтому их часто используют.

Недостатки указателей

Главные недостатки указателей:

1. Нарушение принципов изоляции кода

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

2. Отвлечение внимание на детали реализации

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

3. Плохая читаемость кода

Прямое использование переменной является самоочевидной вещью. Если мы видим x++, то сразу понимаем, что происходит, а вот если мы видим (*px)++ или *px++, то чтобы понять процесс, нужно вдумываться.

Сравним два варианта кода. Код с переменными:

и код с указателями

Мы видим, что с указателями читаемость кода хуже, а преимуществ никаких мы не получили.

Указатели лучше вообще не использовать.

Конечно же, на это последует вопрос: «А как тогда изменять значения внутри функции?»

Этот код поменяет значения любых элементов массива.

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

В правильно спроектированной программе есть три вида элемента данных:

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

Ответы на вопросы

Вопрос

Мы сначала передали адрес a в функцию AddFive, затем создали указатель int px(но почему именно в аргументе функции?), далее значение по адресу указателя увеличили на 5. Но тут непонятно, разве так будет работать? То есть, нужно сначала адрес присвоить указателю, как Вы показывали ранее в статье. Получится вот так:

Ответ

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

Вопрос

Я проверил код в CodeBlocks. Если мы укажем, например, вот так:

то возникнет ошибка: «px undeclared». Т.е. как видите указатель px не объявлен. А чтобы всё работало мы одновременно, c одной стороны, объявляем указатель в аргументе функции AddFive, а с другой стороны, записываем в указатель адрес a. Поэтому, непонятно почему Вы считаете, что ничего там не создаётся. Ведь память под указатель выделилась, так? И как раз, так как мы создали указатель, пусть и в аргументе функции, программа и работает.

Ответ

На этапе компиляции программы при проверке аргументов функции компилятор ничего не создает, а только проверяет соответствие типов аргументов в описании функции и при ее вызове. В данном примере в описании функции нет аргументов, а при вызове передается адрес — это первая ошибка в данном фрагменте. Вторая ошибка заключается в том, что идет обращение к переменной px, но она не объявлена, поэтому компилятор пишет, что «px undeclared», то есть «переменная px не объявлена».

Когда ошибки будут устранены и программа будет запущена, то в момент вызова AddFive(&a) произойдет следующее:

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

Источник

Ответы на самые частые вопросы пользователей рунета
Добавить комментарий

Adblock
detector