как вывести адрес переменной си
Указатели C++, на массив и структуру, ссылки, разыменование, примеры
Итак, небольшая статья на эту страшную и малопонятную новичкам С++ тему.
Допустим, что вы уже знаете, что такое переменная в языке Си/Си++.
1 | 2 | 3 | 4 | . | 3221225472 | . | 8589934592 |
Так уж сложилось, что такие громадные величины лучше удобнее кодировать в 16ричной системе исчисления. 0х1 до 0х200000000(в моем случае, 8ГБ памяти же)
1 | 2 | 3 | 4 | . | 3221225472 | . | 8589934592 |
0х1 | 0х2 | 0х3 | 0х4 | . | 0хC0000000 | . | 0х200000000 |
Вот с номерами ячеек программисту ниразу не удобнее работать, поэтому придумали символьные имена для этих ячеек
1 | 2 | 3 | 4 | . | 3221225472 | . | 8589934592 |
0х1 | 0х2 | 0х3 | 0х4 | . | 0хC0000000 | . | 0х200000000 |
0x00 | char a; | 0хFF | char b; | . | 0xFF | . | 0x31 |
Давайте для удобства превратим ряд в таблицу, стеллаж.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x0000 | a | ||||||||||||||
0x0010 | b | ||||||||||||||
0x0020 | |||||||||||||||
0x0030 | c | ||||||||||||||
0x0040 | |||||||||||||||
0x0050 | |||||||||||||||
0x0060 |
char a; // переменная находится по адресу 0х0004, не проинициализирована
char b; // переменная находится по адресу 0х0018, не проинициализирована
int c=344; // переменная находится по адресу 0х0030, проинициализирована значением 344
Объявляя переменную, мы по сути выделяем ей память определенного размера(1,4,8 байт). Размер зависит от типа данных.
После объявления переменной, в этой занятой ячейке памяти ничего не находится, точнее, там мусор от прошлых использований.
При инициализации переменной, в эту занятую ячейку памяти заносится заданное программой значение.
Процессору без разницы как вы назовете переменную. Он все равно оперирует адресами.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x0000 | a | ||||||||||||||
0x0010 | b | ||||||||||||||
0x0020 | |||||||||||||||
0x0030 | c=344 | ||||||||||||||
0x0040 | *d | ||||||||||||||
0x0050 | *e=&c | ||||||||||||||
0x0060 |
Объявляя переменную-указатель, мы по сути выделяем ей память всегда одинакового размера, необходимого для адресации любой ячейки памяти (4 байта для 32битных систем, 8 байт для 64 битных). Размер зависит от разрядности системы и задается при компиляции программы.
Чтобы узнать адрес любой переменной, используется символ & перед именем.
Чтобы узнать что лежит по адресу в указателе, нужно «разыменовать» его, используя символ * (звездочка).
Если еще непонятно, предлагаю перейти к примерам.
Вот небольшой пример указателей на языке Си++. Желательно самому все проверить(DevCpp)
Кое-что интересно есть в примере.
*(e+1) и e[1] дают идентичный результат.
Есть еще кое-что интересно в примере на C/C++.
a2->a при работе напрямую с указателем на структуру используется стрелка «->»
Указатели на массив.
И на структуру.
Особенности использования в C++.
В массиве можно обратиться к элементам через указатель 2 способами:
В структуре можно обратиться к членам через указатель 2 способами:
Все это справедливо для языка программирования Си++
Указатели в C++: адрес и удаление
Всем привет! В этом уроке мы разберём то, что очень тесно связано с памятью в компьютере. С помощью этого можно улучшать работу своей программы. Как вы догадались с названия урока, это — указатели.
Адрес переменной в C++
Давайте приведем аналогию с круизным лайнером. В нем, как и в отеле, имеются номера. Вот, например, при покупке номера вам могут дать номер — 0x155 (да, мы понимаем, что не в одном лайнере или отеле не станут записывать номера в шестнадцатеричном виде, но давайте все таки немного отвлечемся). А друг может оказаться в номере 0x212 — так и с переменными, они могут получить разный путь. И только сам компьютер при создании переменной знает, где она находится.
Переменные, которые вы создаете в программе, по её завершению автоматически удаляются, чтобы не нагружать операционную память вашего компьютера.
Пример удаления переменных
В играх присутствует хорошая графика, различные спецэффекты. Например, тот же дым. Все это — переменная (может не одна!), которой в будущем придётся уничтожиться навсегда. А вот, если бы она не удалилась, то она бы своей фоновой работой понемножку нагружала бы наш компьютер.
Поэтому в C/C++ присутствует возможность обратиться к переменной, и, если требует ситуация, удалить и создать её вовсе в другом участке программы, когда это, конечно, нам будет нужно.
Что такое указатели в C++
Указатели — это с самого начала переменные, уже в которых хранится адрес других переменных.
Чтобы пользоваться указателями, вам нужно использовать два оператора:
Как создать указатели в C++
Давайте посмотрим, какую конструкцию нужно использовать, чтобы создать указатели:
Указатели
Указатели
Э то, пожалуй, самая сложная и самая важная тема во всём курсе. Без понимания указателей дальнейшее изучении си будет бессмысленным. Указатели – очень простая концепция, очень логичная, но требующая внимания к деталям.
Определение
У казатель – это переменная, которая хранит адрес области памяти. Указатель, как и переменная, имеет тип. Синтаксис объявления указателей
Например
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. Найдём длину строки, введённой пользователем, используя указатель
Переменные, адреса и указатели
Переменная — это область памяти, к которой мы обращаемся за находящимися там данными, используя идентификатор (в данном случае, имя переменной). При этом у этой помеченной именем области есть еще и адрес, выраженный числом и понятный компьютерной системе. Этот адрес можно получить и записать в особую переменную. Переменную, содержащую адрес области памяти, называют указателем.
Когда мы меняем значение обычной переменной, то, можно сказать, просто удаляем из конкретной области памяти данные и записываем туда новые. Когда мы меняем значение переменной-указателя, то начинаем работать с совершенно иным участком памяти, т.к. меняем содержащийся в ней адрес.
Тема указателей тесно связана с темой динамических типов данных. Когда программа компилируется, то под объявленные переменные так или иначе (в зависимости от того, где они были объявлены) выделяются участки памяти. Потом размер этих участков не меняется, может меняться только их содержимое (значения или данные). Однако именно с помощью указателей можно захватывать и освобождать новые участки памяти уже в процессе выполнения программы. Динамические типы данных будут рассмотрены позже.
Прежде чем перейти к рассмотрению объявления и определения переменных-указателей, посмотрим, что из себя представляет адрес любой переменной и как его получить.
В результате выполнения данного программного кода на экране появляется примерно следующее (шестнадцатеричное число у вас будет другим):
Как мы видим, несмотря на то, что значения переменных поменялись, ячейки памяти остались прежними.
Зная адрес, можно получить значение, которое находится по этому адресу, поставив знак * перед адресом:
На экране будет выведено число 8.
Однако запись типа &a не всегда возможна или удобна. Поэтому существует специальный тип данных — указатели, которым и присваивается адрес на область памяти.
Указатели в языке C, как и другие переменные, являются типизированными, т.е. при объявлении указателя надо указывать его тип. Как мы узнаем позже, с указателями можно выполнять некоторые арифметические операции, и именно точное определение их типа позволяет протекать им корректно. Чтобы объявить указатель, надо перед его именем поставить знак *. Например:
Обратите внимание на то, что в данном случае * говорит о том, что объявляется переменная-указатель. Когда * используется перед указателем не при его объявлении, а в выражениях, то обозначает совсем иное — «получить значение (данные) по адресу, который присвоен указателю». Посмотрите на код ниже:
С помощью комментариев указаны текущие значения ячеек памяти. Подробно опишем, что происходит:
Под сам указатель (там, где хранится адрес) также должна быть выделена память. Объем этой памяти можно узнать с помощью функции sizeof() :
Под указатели всех типов выделяется одинаковый объем памяти, т.к. размер адреса не зависит от типа, а зависит от вычислительной системы. В таком случае, зачем при объявлении указателя следует указывать тип данных, на который он может ссылаться? Дело в том, что по типу данных определяется, сколько ячеек памяти занимает значение, на которое ссылается указатель, и через сколько ячеек начнется следующее значение.
Если указатель объявлен, но не определен, то он ссылается на произвольный участок памяти с неизвестно каким значением:
Результат (в Ubuntu):
Использование неопределенных указателей в программе при вычислениях чревато возникновением серьезных ошибок. Чтобы избежать этого, указателю можно присвоить значение, говорящее, что указатель никуда не ссылается (NULL). Использовать такой указатель в выражениях не получится, пока ему не будет присвоено конкретное значение:
Результат (в Ubuntu):
В данном случае, если попытаться извлечь значение из памяти с помощью указателя, который никуда не ссылается, то возникает «ошибка сегментирования».
На этом уроке вы должны понять, что такое адрес переменной и как его получить ( &var ), что такое переменная-указатель ( type *p_var; p_var = &var ) и как получить значение, хранимое в памяти, зная адрес ячейки ( *p_var ). Однако у вас может остаться неприятный осадок из-за непонимания, зачем все это надо? Это нормально. Понимание практической значимости указателей придет позже по мере знакомства с новым материалом.
Практически проверьте результат работы всех примеров данного урока, придумайте свои примеры работы с указателями.
Указатель в языке Си
Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.
Указатели широко используются в программировании на языке Си.
Указатели часто используются при работе с массивами.
Память компьютера можно представить в виде последовательности пронумерованных однобайтовых ячеек, с которыми можно работать по отдельности или блоками.
Каждая переменная в памяти имеет свой адрес — номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).
Указатель, как и любая переменная, должен быть объявлен.
Общая форма объявления указателя
Тип указателя — это тип переменной, адрес которой он содержит.
Для работы с указателями в Си определены две операции:
Для указанного примера обращение к одним и тем же значениям переменной и адреса представлено в таблице
Расположение в памяти переменной a и указателя b:
Необходимо помнить, что компиляторы высокого уровня поддерживают прямой способ адресации: младший байт хранится в ячейке, имеющей младший адрес.
Комментариев к записи: 80
// Функция кодирования текста
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;
>