Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 212 сообщений
Cообщения с меткой

указатели - Самое интересное в блогах

Следующие 30  »
Автосенсей

21. Начало движения от тротуара — Avtosensei

Четверг, 22 Октября 2015 г. 17:00 (ссылка)
avtosensei.ru/video/21-nach...?_utl_t=li


21. Начало движения от тротуара — Avtosensei

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
Оля-душка

20 забавных указателей населенных пунктов на наших дорогах

Воскресенье, 18 Октября 2015 г. 13:16 (ссылка)



















Привет дорогие друзья !!В каждом районе есть интересные названия  населённых пунктов.   У нас в Курской области есть населённые пункты Косторное ,Дряблово ,Большежирово,Солнцево ,Ястребовка  .А чем вы можете удивить меня:)



 



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




20 забавных указателей населенных пунктов на наших дорогах




20 забавных указателей населенных пунктов на наших дорогах

Читать далее...
Метки:   Комментарии (28)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Объекты нулевого размера

Суббота, 17 Октября 2015 г. 13:39 (ссылка)

В чём разница между следующими парами длин и указателей?



size_t len1 = 0;
char *ptr1 = NULL;

size_t len2 = 0;
char *ptr2 = malloc(0);

size_t len3 = 0;
char *ptr3 = (char *)malloc(4096) + 4096;

size_t len4 = 0;
char ptr4[0];

size_t len5 = 0;
char ptr5[];




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



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



malloc(0)



Поведение malloc(0) определено стандартами. Можно вернуть нулевой или уникальный указатель. Второй вариант во многих реализациях выполняется внутренним увеличением длины на единицу (которая затем обычно округляется до 16). По правилам, разыменовать такой указатель нельзя, но обычно несколько байт всё-таки размещаются, и поэтому такая программа не упадёт.



Возврат NULL приводит к возможности возникновения интересного бага. Часто возврат NULL из malloc расценивается как ошибка.



if ((ptr = malloc(len)) == NULL)
err(1, "out of memory");




Если len равна нулю, это приведёт к неправомерному сообщению об ошибке – если не добавить дополнительную проверку && len != 0. Также можно вступить в секту адептов «непроверки malloc».



В OpenBSD malloc обрабатывает ноль по-другому. Размещение данных нулевого размера возвращает куски страниц, которые были защищены через mprotect() с ключом PROT_NONE. Попытка разыменовать такой указатель приведёт к падению.



Отметим, что требования к уникальным указателям запрещают «мухлевать» при их использовании.



int thezero;

void *
malloc(size_t len)
{
if (len == 0) return &thezero;
}
void
free(void *ptr)
{
if (ptr == &thezero) return;
}




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



Другие случаи



Если malloc не выдаст ошибку, то варианты 3, 4 и 5 в большинстве случаев работают идентично. Основное отличие будет в использовании sizeof(ptr) / sizeof(ptr[0]), например в цикле. Это приведёт к неверному ответу, правильному ответу или вообще ни к чему не приведёт, обломавшись на этапе компиляции. 4-й вариант не разрешён стандартом, но компиляторы, скорее всего, его проглотят.



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



null-объекты



Вернёмся к первому варианту и нулевым объектам. Рассмотрим следующий вызов:



memset(ptr, 0, 0);




0 байт ptr присваиваем 0. Какие из пяти перечисленных указателей позволят сделать такой вызов? 3, 4 и 5. 2-й – если это уникальный указатель. Но что, если ptr это NULL?



В стандарте С в разделе «Использование библиотечных функций» сказано:

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


В разделе «Соглашения по функциям работы со строками» уточняется:

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


Судя по всему, результат memset'а 0 байт на NULL будет неопределённым. В документации по memset, memcpy и memmove не указано, что они могут принимать нулевые указатели. В качестве контрпримера можно привести описание snprintf, в котором сказано: «Если n равен нулю, ничего не записывается, и s может быть нулевым указателем». Документация к функции read из POSIX похожим образом описывает, что чтение нулевой длины не считается ошибкой, но реализация может проверить другие параметры на ошибку – например, на недопустимые буферные указатели.



А что на практике? Самый простой способ обработки нулевой длины в функциях типа memset или memcpy – не входить в цикл и ничего не делать. Обычно в С неопределённое поведение вызывает какую-либо реакцию, но в данном случае уже определено, что с нормальными указателями ничего не происходит. Проверка на ненормальность указателей была бы лишней работой.



Проверка на ненулевые, но недопустимые указатели, довольно сложна. memcpy этим вообще не занимается, позволяя программе просто упасть. Вызов read тоже ничего не проверяет. Он делегирует проверку функции copyout, которая заводит хэндлер для обнаружения ошибок. И хотя можно добавить проверку на null, такие указатели не более недопустимы для этих функций, нежели 0x1 или 0xffffffff, для которых нет никакой особой обработки.



Облом



На практике это означает наличие большого количества кода, подразумевающего (специально или случайно), что нулевые указатели и нулевая длина допустимы. Я решил провести эксперимент, добавив в memcpy вывод ошибок и выход, в случае, если указатель оказывается NULL, и установил новую libc.



Feb 11 01:52:47 carbolite xsetroot: memcpy with NULL
Feb 11 01:53:18 carbolite last message repeated 15 times




Нда, это не отняло много времени. Интересно, что он там делает:



Feb 11 01:53:18 carbolite gdb: memcpy with NULL
Feb 11 01:53:19 carbolite gdb: memcpy with NULL




Ясно, понятно. Эти сообщения, похоже, очень быстро надоедят. Возвращаем всё, как было.



Последствия



Я занялся эти вопросом, поскольку на пересечении областей «не определено, но должно работать» и оптимизации компиляторов С не происходит ничего хорошего. Умный компилятор может увидеть вызов memcpy, отметить оба указателя, как допустимые, и убрать проверки на null.



int backup;
void
copyint(int *ptr)
{
size_t len = sizeof(int);
if (!ptr)
len = 0;
memcpy(&backup, ptr, len);
}




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



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



Поначалу мне не удавалось убедить компилятор удалять проверку на ноль после «разыменования» memcpy, но это не значит, что этого не может случиться. gcc 4.9 говорит, что эта проверка будет удалена оптимизацией. В OpenBSD пакет gcc 4.9 (содержащий множество патчей) не удаляет по умолчанию проверку, даже при –O3, но если разрешить "-fdelete-null-pointer-checks", это приводит к удалению проверок. Не знаю, что насчёт clang – первые тесты показывают, что не удаляет, но гарантий нет. В теории он тоже сможет провести такую оптимизацию.

Original source: habrahabr.ru (comments, light).

http://habrahabr.ru/post/268989/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
АртСвалка

Указатели, знаки и таблички фотографии | Signs, signs photos

Пятница, 14 Августа 2015 г. 21:00 (ссылка)


Имя архива: 1437563470_583908_archive.zip | Количество файлов: 6 | Размер архива: 47.63 Мб
Просмотреть новость полностью

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
АртСвалка

Деревянные вывески и указатели | Wooden signs and signs

Пятница, 14 Августа 2015 г. 20:00 (ссылка)


Имя архива: 1437562091_905721_archive.zip | Количество файлов: 9 | Размер архива: 161.19 Мб
Просмотреть новость полностью

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
АртСвалка

Иконки, указатели, графики, диаграммы, электроника в векторе | The icons, signs, graphs, charts, electronics in vector

Четверг, 09 Июля 2015 г. 05:00 (ссылка)


Имя архива: 1434648691_264536_archive.zip | Количество файлов: 4 | Размер архива: 0.82 Мб
Просмотреть новость полностью

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
NikNikAnn

Таблички для бани своими руками

Суббота, 23 Мая 2015 г. 10:55 (ссылка)


Хотел бы представить для Вас свой «проектик» и последовательность выполнения резьбы в моем стиле.



1.Шаг первый. Начало действий



 





 



для начала осуществления задуманного мне приглянулась такая вот доска - приступок хвойной породы и  стандартного размера во всем известном магазине "леруа мерлен" . С этим материалом я и буду работать следующие 4 часа)



2. Шаг второй. Разметка, распил



 





Далее
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Указатели, массивы и структуры C. Приподнимаем завесу

Четверг, 07 Мая 2015 г. 17:21 (ссылка)

image



«Язык Си как острая бритва: с его помощью можно сделать изящное произведение искусства или кровавое месиво.»— Керниган


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



Небольшое отступление про типы и переменные в C



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



	char - 1 байт
short - 2 байта
int - 4 байта (2 байта на 16 и некоторых 32 битных архитектурах; исторически со времен Ритчи и Томпсона- 2 байта)
long - 4 байта (8 байт на некоторых 64 битных архитектурах)




Как видим — бардак. Один и тот же код может скомпилиться на разных архитектурах по-разному.

Поэтому, когда нам нужна именно 32 битная (или еще какая-нибудь конкретная) переменная…



	/* ...мы инклудим: */
#include

/* и получаем вот такие замечательные типы: */

int8_t a, int16_t b, int32_t c, int64_t d; // Знаковые
uint8_t e, uint16_t f, uint32_t g, uint64_t h; // и беззнаковые




Указатели



Указатель это целочисленная беззнаковая переменная, предназначенная для хранения адреса объекта в памяти и обеспечении доступа к нему. Размер указателя обычно равен разрядности вычислительной машины. Для 32 битного указателя диапазон его возможных значений будет: {0x00000000… 0xFFFFFFFF}, для 64 битного {0x0000000000000000… 0xFFFFFFFFFFFFFFFF} соответственно.



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



Объявление указателей


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



	#include 
#include
#include

int main() {
/* p - указатель на переменную типа int32_t,
e - просто переменная, т.к. перед ней нет звездочки! */
uint32_t *p, e;




Чтение/запись значения указателей


Несмотря на то, что указатель по своей сути всего лишь uint32_t/uint64_t переменная, компилятор в целях нашей же безопасности, не позволит нам делать с ним все то что мы привыкли делать с обычными переменными. Так, например, попытка умножить указатель на что-либо вызовет ошибку компиляции. Так что же мы можем делать с указателями? В первую очередь, присваивать и читать их значения, и здесь все почти как с обычными переменными:



        /* присвоим указателю произвольное значение */
p = 5;

/* теперь указатель p содержит адрес 0x00000005, в чем можем легко убедиться */
printf("pointer p = %08X\n", p);




Если мы скомпилируем такой код, то скорее всего получим предупреждение компилятора, вроде такого:

	warning: incompatible integer to pointer conversion assigning to 'int *' from 'int'
Дело в том, что мы пытаемся присвоить указателю произвольное числовое значение, что вызывает у него вполне обоснованные подозрения в том что мы что-то перепутали.

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



Явное приведение типов в си производится указанием типа в круглых скобках перед значением. Неявное — осуществляется автоматически, там где это возможно. Например, когда мы передаем char переменную в функцию принимающую int преобразование char к int произойдет автоматически. В случае же попытки передать int в функцию принимающую char компилятор сгенерирует предупреждение, поскольку в int при этом будет отброшены все байты кроме младшего.



    /* явно приведем тип нашей пятерки (int) к типу "указатель на int" */
p = (int *)5;




Теперь наш код абсолютно валиден как с точки зрения компилятора, так и с точки зрения того кому доведется его читать. Однако, если мы обратимся к памяти по адресу 5, MMU обнаружит попытку доступа к не принадлежащей нам памяти, и у нас возникнет runtime error. Поэтому лучше попросим стандартную функцию malloc() выделить нам кусочек памяти, достаточный для размещения в нем одной int32_t переменной:



	p = malloc(4); /* выделим участок памяти в 4 байта и сохраним его адрес в p */

/* и распечатаем полученный нами адрес */
printf("pointer p = %08X\n", p);




Работа с указуемым объектом (Разименовывание указателя)


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

Для хранения адреса нам запросто бы хватило обычной uint32_t переменной. Но прелесть указателей заключается в том, что он позволяет нам обратиться к адресуемому объекту. Для того чтобы сказать компилятору что мы хотим работать не с самим указателем, а с тем на что он указывает, перед именем указателя нужно поставить звездочку. Это называется разименовыванием указателя, и это третья разрешенная операция для указателей (после операций чтения и записи). Для начала, посмотрим что лежит в выделенной нам памяти:



	printf("*p = %08X\n", *p);


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



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



	*p = 0x1234;


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



	printf("*p = %08X\n", *p);


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



Массивы и указатели



В си массив представляет собой кусок памяти размер которого равен произведению рамзера элемента на количество элементов в массиве. Так, массив:



	uint32_t m[2];
m[0] = 0x01234567;
m[1] = 0x89ABCDEF;


Будет занимать в памяти sizeof(unsigned int) * 2 = 4 * 2 = 8 байт, и после выполнения вышеприведенного кода будет выглядеть в памяти следующим образом:



      adderss   value
[base+0000] 67
[base+0001] 45
[base+0002] 23
[base+0003] 01
[base+0004] EF
[base+0005] CD
[base+0006] AB
[base+0007] 89




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

Массив в Си это просто указатель


Когда мы обращаемся к n-ному элементу массива компьютер умножает индекс (n) на размер одного элемента массива — таким образом он получает смещение начала индексируемого элемента от начала массива. Затем компьютер складывает это смещение с указателем на массив и таким образом получает адрес индексируемого элемента в памяти. Далее этот адрес разименовывается и мы работаем уже с самим элементом!



Например, для доступа к m[1] в вышеприведенном примере, компьютер умножит sizeof(unsigned int) на 1 и получит смещение: 4*1 = 4. Далее он прибавит это смещение к базе: base+0004 и получит адрес 1-го элемента массива (не путать с нулевым элементом!). Это происходит всякий раз, когда вы обращаетесь к элементу массива.



Приведенный ниже код призван продемонстрировать то, что массивы в Си являются указателями, а операция индексирования [] в си определена для указателей вообще:



	unsigned int *k;
k = m; /* присваивание значения указателя (массива, что одно и то же) новому указателю */

/* сейчас мы пишем все в тот же массив m! */
k[0] = 0x00000000;
k[1] = 0x11111111;




Как можно заметить, массивы в Си устроены очень просто, и в их основе лежат указатели.



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





Как уже было ранее сказано, с указателями мы можем делать три вещи:


  • читать

  • писать

  • разименовывать



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



	/* Сейчас k еще указывает на m, что эквивалентно указанию на m[0] */

k = k + 1; /* сдвинем k на 1 элемент вперед */




Теперь k указывает на m[1]! Это означает то, что если мы сделаем



printf("k[0] = %08X\n", k[0]);


То на экране появится 11111111, а не 00000000. Указатель сместился вперед на один элемент и первый элемент массива m остался позади, а второй стал для него первым. В прочем, мы все еще можем получить доступ к первому элементу массива m через указатель k:



printf("k[-1] = %08X\n", k[-1]);


Забавно, не правда ли? Ладно, поигрались и хватит :) Давайте вернем наш указатель назад.



	/* Сейчас k указывает на m[1], заставим его снова указывать на m[0] */

k = k - 1; /* просто сдвинем k на 1 элемент назад */

/* проверочка! */
printf("k[0] = %08X\n", k[0]);




Теперь все вернулось на первоначальное место и k[0] снова равен 0x00000000.



А что если мы сейчас просто разименуем указатель k?



	printf("*k = %08X\n", *k);




Если вы выполните этот пример, то увидите, что *k и k[0] это одно и то же!

(Индексация это вычисление смещения до элемента, сложение смещения с указателем и разименовывание. Смещение равно 0, остается лишь разименовывание!)



Важно помнить, что прибавляя (или отнимая) к указателю единицу мы фактически прибавляем к нему единицу умноженную на размер типа на который он указывает!


Если вам необходимо сместить какой либо указатель на n байт, например указатель на int на 5 байт, то вы столкнетесь с проблемой: прибавляя к такому указателю 1 вы фактически будете прибавлять к нему 4, прибавляя 2 — 8, ит.д. Для того чтобы это сделать необходимо временно преобразовать тип указателя к указателю на char, и только тогда прибавлять к нему 5.



Еще один пример: индексация массива без помощи квадратных скобок. К примеру, хотим распечатать значение i-того элемента массива k:



	int i;
...
printf("k[i] = %08X\n", i, *(k + i));




Здесь мы вручную складываем указатель со смещением и разименовываем получившийся указатель.



Далее нами будут рассмотрены указатели на структуры (struct) и функции, и здесь следует заметить, что прибавление (или вычитание) единицы к указателю на структуру прибавит к нему размер этой структуры. То есть, инкрементируя/декрементируя указатель на структуру мы перемещаемся вперед-назад по массиву структур. Аналогично ведут и себя указатели на функции.



Структуры Cи (struct)



Структуры Cи — аналог записей в паскале или объектов в ООП языках. Отличие от объектов заключается в том, что функции работающие со структурой описываются отдельно от нее. Структура в своей реализации очень похожа на массив. Разница лишь в том, что составные элементы структуры (поля или свойства) могут иметь разный размер, в то время как базовой идеей массива является то что все элементы одинаковы (а как иначе организовать простое индексирование массива?). Но доступ к полям структуры осуществляется не по индексу, а по имени. Опишем новую структуру в глобальной области, где нибудь перед функцией main():



	struct xx {
uint8_t a;
uint8_t b;
uint32_t c;
};




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



	/* Объявим указатель на нашу структуру */
struct xx *xx_ptr;

/* и выделим под нее участок памяти */
xx_ptr = malloc(sizeof(struct xx));




Доступ к элементам структуры по указателю осуществляется с помощью оператора ->:



	/* Запишем что нибудь в нашу структуру */
xx_ptr->a = 0xAA;
xx_ptr->b = 0xBB;
xx_ptr->c = 0x12345678;




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



	/* Заведем еще один - байтовый указатель, возьмем адрес нашей структуры
и пройдемся по ней, распечатывая каждый байт */

uint8_t *byte_ptr;
byte_ptr = (uint8_t *) xx_ptr;

int i;
while(i < sizeof(struct xx)) {
printf("[%04d] %02X\n", i, byte_ptr[i]);
++i;
}




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

	offset value
[0000] AA <- a
[0001] BB <- b
[0002] 00 <- {!padding}
[0003] 00 <- {!padding}
[0004] 78 <- c
[0005] 56 |
[0006] 34 |
[0007] 12 <--+




Стоп, скажете вы. Почему между полями b и с возник разрыв в 2 байта? Ответ на этот вопрос состоит в том, что современным процессорам гораздо удобнее работать с т.н. выровненными данными (Расставим точки над структурами C/C++). И данный padding в 2 байта заложенный компилятором позволит вашему процессору гораздо быстрее работать с такой слегка раздутой структурой.



В то же время, если на первом месте стоит не скорость, а память, и вы имеете over9000 таких структур, то значительный кусок памяти будет пропадать вот на таких padding'ах. Для того чтобы заставить компилятор паковать все именно так как вы написали (а это порой бывает очень важно, например в драйверах аппаратных устройств), в GCC существует атрибут packed:



	struct xx {
char a;
char b;
unsigned int c;
} __attribute__ ((__packed__));




В других компиляторах данный атрибут выставляется с помощью директивы компилятора #pragma pack.



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



	offset value
[0000] AA <- a
[0001] BB <- b
[0002] 78 <- c
[0003] 56 |
[0004] 34 |
[0005] 12 <--+




Указатели на функции



Указатели на функции декларируются несколько замысловато:



	int (*func_ptr)(int a, int b);


Выше приведен указатель на функцию принимающую 2 int аргумента и возвращающую int.



— Заверните два!

— Легко!



	int (*func_ptr)(int (*func)(int), int k);


Указатель на функцию возвращающую int и принимающую (функцию принимающую int и возвращающую int) и int.



Вот такой он простой, наш Cи!



А вот вам массив из ста указателей на функцию:



	int (*func_ptr[100])(int a, int b);


Спросите и кому такое может понадобиться? — На самом деле, очень полезная штука. Лично я применял такой массив при создании виртуальной машины. Работает куда быстрей, чем switch диспетчер. Думаю, и вам пригодится.



Инициализировать указатель на функцию очень просто, достаточно просто присвоить ему желаемую функцию, например, так:

	void (*func_ptr)();

func_ptr = main; /* теперь func_ptr указывает на main */

func_ptr(); /* вызовем main() через указатель */




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



Указатели могут указывать на что угодно, что есть в памяти. Раз они сами находятся в ней, логично что и на себя. Зачем? Все очень просто — вот устройство двумерного массива:

	ptr -----> [ptr, ptr, ptr, ...]
| | | ...
[a] [a] [a]
[r] [r] [r]
[r] [r] [r]
[a] [a] [a]
[y] [y] [y]
[1] [2] [3]
... ... ... ...


То есть — двумерный массив в Си это массив указателей на массивы данных. Указатель указывает на массив указателей, каждый из которых указывает на свою колонку двумерной матрицы. Когда просто работаешь с массивом об этом лучше не думать, чтобы не впасть в долгие и непродуктивные размышления о природе бытия :D.



Указатели на указатели настолько распространены, что встречаются даже в обычной unix main():



	int main(int argc, char **argv) {


Здесь argv — указатель на массив указателей на строки аргументов командной строки.



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

	char *p1;		/* первый уровень */
char **p2; /* второй уровень */
char ***p3; /* третий уровень */




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



	*p1 = 'A';
**p2 - 'B';
***p3 = 'C';




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



Операция взятия адреса



Создавать новые объекты это хорошо, но что, если мы хотим получить адрес уже имеющегося объекта, например, параметра функции, локальной или глобальной переменной? Это возможно, и си предоставляет нам для этих целей оператор & (амперсанд) позволяющий получить адрес чего угодно, если это имеет адрес. Нельзя например взять адрес литеральной константы &5 и нельзя взять адрес результата выражения &(2+2) так как константа нигде не хранится; а результат выражения находится в регистре процессора, у которого как известно нет адреса, ит.п. Далее я приведу сжатый пример того как можно брать адрес различных объектов.



	struct x {
int a;
};

/* Глобальные переменные */
struct x x1;
struct x *xp;
int k;
char c[100];

int main() {
/* указатели на разные типы */
struct x *x_ptr;
struct x **x_ptr_ptr;
int *int_ptr;
char *char_ptr;

xp = malloc(sizeof(struct x));

x_ptr = &x1; /* берем адрес глобальной структуры */
x_ptr = xp; /* берем адрес структуры, лежащий в другом указателе */
x_ptr_ptr = &xp; /* берем адрес глобального указателя. адрес. указателя. берем :) */
int_ptr = &x1.a; /* берем адрес поля глобальной структуры */
int_ptr = &xp->a; /* берем адрес поля структуры на которую указывает глобальный указатель */
int_ptr = &k; /* берем адрес глобальной переменной */
char_ptr = &c; /* берем адрес глобального массива */
char_ptr = c; /* и так тоже можно, т.к. массив это указатель */
char_ptr = &c[50]; /* берем адрес элемента массива с индексом 50 */

return 0;
}




Void — указатели



Язык Си не позволяет пользователю создавать void переменные (зачем?), зато позволяет создавать указатель на void. За таким указателем не закреплен конкретный тип, но каждый раз перед разименовыванием такого указателя вам придется приводить его к желаемому типу вручную. А как иначе компилятор узнает что вы хотите разименовать и как это сделать?



Функция malloc() определена как void *malloc(int size); что позволяет присваивать результат ее работы любому типу указателя без каких бы то ни было предупреждений со стороны компилятора.



Константа NULL тоже имеет тип void:

	#define NULL ((void *)0x0)




Заключение



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

Original source: habrahabr.ru (comments, light).

http://habrahabr.ru/post/257485/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
bira7

Клипарт в PNG - Указатели, вывески и щиты

Среда, 04 Марта 2015 г. 22:55 (ссылка)

день рождения
Клипарт в PNG - Указатели, вывески и щиты
138 PNG | 300 x 600 - 3000 x 2500 px | 60,4 Mb

Читать далее...
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<указатели - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda