DobrijZmej 23.11.2010 18:57
Coding — Язык C и указатели
Про указатели в C уже говорено-переговорено. Но недавно они мне пригодились.Хочу рассказать новичкам, как именно пригодились.
Долгое время я путался (да и сейчас путаюсь) в этим порождении C - указатель.
Но, на этих выходных, я нашел очень удобное средство его применения. На самом деле, я думаю этих средств намного больше, но это пока что в первый раз где это мне пригодилось. Правда, и полноценно изучать С я только начал.
Итак, где-же мне это пригодилось ?
Для начала немного теории от digiwhite
Указатель представляет собой место, куда может быть записан адрес памяти.
Указатель у современных архитектур и компиляторов может содержать 2^32 значение, т.е. имеет размерность 4 байта (на 64-х разрядных архитектурах - вероятно это будет 8 байт - т.е возможно записать 2^64 значений).
Над указателем можно выполнять операцию разыменовывания указателя '*' и взятия адреса '&'. Пример:
Над указателями можно выполнять арифметические операции, такие как: сложение, вычитание, инкремент и декремент. Однако, тут есть зависимость от того, на какой тип данных указывает указатель. Т.е. операции над указателем переведу к изменению адреса на величину типа данных, на который указывает указатель, умноженное на число, на которое нужно увеличить/уменьшить указатель.
Продолжаем практику =)
У меня есть функция, которая в зависимости от результата возвращает правду или ложь.
Но, кроме того, она перемещается по переданному ей файлу, и прикольно было-бы, если бы она возвращала текст, который читает из этого файла.
Итак, теперь поэтапно, как я к этому пришел.
Начну с малого. Давайте разберемся с указателями.
1 |
|
Это еще не указатель. Это целочисленная переменная, расположенная где-то в памяти (в данном конкретном случае - в стеке), и имеющая значение 5.
Для простоты примера, скажем, что она находиться в памяти по адресу 0x0050.
Т.е. в памяти по адресу 0х0050 находится число 5.
Теперь мы хотим передать эту переменную в функцию, к примеру так:
1 |
|
При входе в функцию создается локальная (в области видимости) копия стека, куда повторно заноситься значение переменной i по новому адресу.
Если внутри фунции printf переменная i будет как-то меняться (ну предположим), то на стек основной программы она никак не повлияет, и как бы она внутри функции не менялась - в основной программе переменная i не измениться.
Нужно это как-то проверить. Напишем исходник.
1) Введем переменную, и посмотрим на каком она адресе:
Создаем файлик main.c с содержимым:
Для компиляции я использовал команду:
1 |
|
После того, как она отработала, в каталоге с файлом main.c у меня появился исполняемый файл a.out. Его и запускаем на выполнение командой:
1 |
|
Результатом его работы будет 2 строчки:
1 |
|
Первая строка - это значение переменной i.
Вторая строка - это ее адрес в памяти. Вот это выражение "&i" уже и есть указатель на адрес размещения переменной i в памяти стэка.
2) Попробуем теперь эту переменную передать в функцию, и распечатать ее уже оттуда:
Компилируем еще раз, запускаем, и получаем следующий вывод:
1 |
|
Как видим, новая переменная находится совершенно по другому адресу.
3) Давайте попробуем изменить переменную внутри нашей процедуры:
Запускаем, смотрим результат:
1 |
|
Т.е. внутри функции мы переменную изменили, но в главной процедуре значение i не поменялось. Как же нам изменить эту переменную изнутри функции ? Вот тут на помощь нам могут прийти указатели.
4) Мы можем передать в функцию не саму переменную, а адрес в памяти, где она будет находиться. И тогда функция будет работать с этим адресом, а не со своей копией переменной.
Компилируем, запускаем и получаем результат:
1 |
|
Как раз то, что от нас и требовалось. Мы изменили переменную во внутренней функции (через указатель на нее), и она изменилась в главной процедуре. Здесь главное не запутаться в переменных, а-то очень просто получить access violation доступа к памяти.
PS: Ну, и в конце, если дозволено будет КА, немного самопиара =) Мысли Доброго Змея: Язык C и указатели
Еще планирую на днях написать статью по поводу указателях в массивах char. Указатель на строку в С сам по себе является указателем, и его не так просто изменить из вызываемой функции.
И еще, если кого заинтересует тема - объясните, плз, когда следует выделять переменным память, а когда достаточно просто размещать их в стэке.
Спасибо, хорошее дополнение к указателям.
По поводу 4-го пункта хочу эту тему (во всяком случае инкремент и декремент) освятить во второй части: работа с char*
Про память: да, понятно, что используется malloc в размере типа, на который указывает указатель.
Но вопрос у меня несколько в ином: есть ли какие правила размещения переменных в стеке, или в выделенной памяти.
Ну, к примеру, некие большие массивы - понятно, что лучше выделять для них память. Но есть ли еще какие рекомендации ?
По поводу 4-го пункта хочу эту тему (во всяком случае инкремент и декремент) освятить во второй части: работа с char*
Про память: да, понятно, что используется malloc в размере типа, на который указывает указатель.
Но вопрос у меня несколько в ином: есть ли какие правила размещения переменных в стеке, или в выделенной памяти.
Ну, к примеру, некие большие массивы - понятно, что лучше выделять для них память. Но есть ли еще какие рекомендации ?
Ну, чего-то каких-то рекомендаций в память не приходит. Здравый смысл если только :).
По идее чем меньше локальных переменных и параметров у функции, тем лучше, т.к. перед вызовом функции надо все параметры запихнуть в стек, все локальные переменные так же поместить в стек, потом вызвать функцию, по завершению работы надо все это дело из стека убрать. По идее, это по одной команде ассемблера на параметр, на локальную переменную и затем вызов функции. Потом в обратном порядке (только без вызова функции). Но это все приблизительно и конкретными фактами не подкреплено. Скорее теория :). Так-что то, что я сказал, надо еще 10 раз проверить.
По идее чем меньше локальных переменных и параметров у функции, тем лучше, т.к. перед вызовом функции надо все параметры запихнуть в стек, все локальные переменные так же поместить в стек, потом вызвать функцию, по завершению работы надо все это дело из стека убрать. По идее, это по одной команде ассемблера на параметр, на локальную переменную и затем вызов функции. Потом в обратном порядке (только без вызова функции). Но это все приблизительно и конкретными фактами не подкреплено. Скорее теория :). Так-что то, что я сказал, надо еще 10 раз проверить.
Поправки:
Перед вызовом функции нужно поместить в стек только аргументы. Место для всех локальных переменных выделяется одной операцией, сдвигом указателя стека на сумму указателей на аргументы и локальные переменные.
При возврате из функции место освобождается одной операцией - изменением указателя стека на нужную величину величину.
Это все недорогие операции. ИМХО, операции на куче могут быть дороже, особенно когда память фрагментирована. Но тут я не очень уверен.
Перед вызовом функции нужно поместить в стек только аргументы. Место для всех локальных переменных выделяется одной операцией, сдвигом указателя стека на сумму указателей на аргументы и локальные переменные.
При возврате из функции место освобождается одной операцией - изменением указателя стека на нужную величину величину.
Это все недорогие операции. ИМХО, операции на куче могут быть дороже, особенно когда память фрагментирована. Но тут я не очень уверен.
хм... ну вообще да, если для каждой переменной выделять память, и не забывать ее освобождать, то это может оказаться дороже.
Спасибо за обсуждение, лучше начал понимать физику процесса =)
Спасибо за обсуждение, лучше начал понимать физику процесса =)
Перед вызовом функции нужно поместить в стек только аргументы. Место для всех локальных переменных выделяется одной операцией, сдвигом указателя стека на сумму указателей на аргументы и локальные переменные.
При возврате из функции место освобождается одной операцией - изменением указателя стека на нужную величину величину.
Спасибо, тоже буду лучше знать :). Отсюда в общем-то следует, почему не стоить делать функции с кучей параметров. Лучше по возможности объединить это дело в структуры и передавать по указателю.
Это все недорогие операции. ИМХО, операции на куче могут быть дороже, особенно когда память фрагментирована. Но тут я не очень уверен.
Думаю, что очень многое тут зависит от алгоритма выделения памяти и его реализации. Ну и еще так же от способа работы с памятью (ну т.е выделяем ли мы по 1 байту сто раз, или же выделили 1 раз 100 байт и работаем с этим куском как нам нужно).
> Отсюда в общем-то следует, почему не стоить делать функции с кучей параметров.
> Лучше по возможности объединить это дело в структуры и передавать по указателю.
Я бы не стал делать уж прямо такой жесткий вывод. Тут мы имеем противоречие между быстродействием и прозрачной и логичной архитектурой программы. В 99% случаев, если пишем на Си, быстродействия хватает. А вот прозрачности часто - нет.
Поэтому, я считаю, что передавать в функцию переменную по указателю нужно только в обоснованном случае и быстродействие среди причин тут на последнем месте. Я вижу такие обоснованные причины:
- Нужно обработать сложную структуру данных.
- Нужно вернуть из функции несколько независимых результатов.
Ну и наверняка я не все знаю.
> Лучше по возможности объединить это дело в структуры и передавать по указателю.
Я бы не стал делать уж прямо такой жесткий вывод. Тут мы имеем противоречие между быстродействием и прозрачной и логичной архитектурой программы. В 99% случаев, если пишем на Си, быстродействия хватает. А вот прозрачности часто - нет.
Поэтому, я считаю, что передавать в функцию переменную по указателю нужно только в обоснованном случае и быстродействие среди причин тут на последнем месте. Я вижу такие обоснованные причины:
- Нужно обработать сложную структуру данных.
- Нужно вернуть из функции несколько независимых результатов.
Ну и наверняка я не все знаю.
Поэтому, я считаю, что передавать в функцию переменную по указателю нужно только в обоснованном случае и быстродействие среди причин тут на последнем месте. Я вижу такие обоснованные причины:
- Нужно обработать сложную структуру данных.
- Нужно вернуть из функции несколько независимых результатов.
Забыли про строки.
По поводу возможности объединения в структуры, я имел в виду то, что объединять нужно то, что логически этому способствует.
Если речь идет о языке C, то:
Спасибо, поправил.
Можно ли текст Вашего коммента включить в статью ?
> И еще, если кого заинтересует тема - объясните, плз, когда следует выделять переменным
> память, а когда достаточно просто размещать их в стэке.
Не понял вопрос. Для простых программ самому думать об этом не следует. Этим занимается компилятор. Насколько я ничего не помню:
Статические и глобальные переменные компилятор размещает в памяти, автоматические и параметры функций в стеке.
Статические - объявляются внутри функций, с модификатором static. Глобальные - вне функций.
Автоматические объявляются внутри функций (без модификатора static).
> память, а когда достаточно просто размещать их в стэке.
Не понял вопрос. Для простых программ самому думать об этом не следует. Этим занимается компилятор. Насколько я ничего не помню:
Статические и глобальные переменные компилятор размещает в памяти, автоматические и параметры функций в стеке.
Статические - объявляются внутри функций, с модификатором static. Глобальные - вне функций.
Автоматические объявляются внутри функций (без модификатора static).
Советую просто прочитать Керниган, Ритчи "Язык программирования Си" - все вопросы сами собой отпадут ;)
Для того, чтобы хорошенько разобраться, полезно все-таки, кроме чтения еще и обсудить с друзьями.
"Язык С и указатели" звучит как-то странно. Имею в виду, что вся мощь и смысл языка С лежит в этих указателях, ну еще '=' сделали операцией, а не оператором.
Ни одна программа, имею в виду более менее что-то делающая, не обходиться без указателей. Взять к примеру знаменитую "hello, world!", да-да! даже там есть указатель!
И собственно отвечу на вопрос: Динамической памятью, то бишь функциями malloc, realloc и free, стоит пользоваться когда заранее размер входных данных не известен. Это конечно не критерий, но для начала думаю сойдет.
Ни одна программа, имею в виду более менее что-то делающая, не обходиться без указателей. Взять к примеру знаменитую "hello, world!", да-да! даже там есть указатель!
И собственно отвечу на вопрос: Динамической памятью, то бишь функциями malloc, realloc и free, стоит пользоваться когда заранее размер входных данных не известен. Это конечно не критерий, но для начала думаю сойдет.
Если речь идет о языке C, то:
Ну, по указателям следует сказать следующее:
Над указателем можно выполнять операцию разыменовывания указателя '*' и взятия адреса '&'. Пример:
char ch='t';
char* ch_ptr = &ch;
fprintf(stdout, "This is the char '%c'.\n", *ch_ptr);
fprintf(stdout, "This is the char '%c' memory address %p.\n", *ch_ptr, ch_ptr);
fprintf(stdout, "This is the memory addres %p of pointer %p which point to char %c.\n", &ch_ptr, ch_ptr, *ch_ptr);
Над указателями можно выполнять арифметические операции, такие как: сложение, вычитание, инкремент и декремент. Однако, тут есть зависимость от того, на какой тип данных указывает указатель. Т.е. операции над указателем переведу к изменению адреса на величину типа данных, на который указывает указатель, умноженное на число, на которое нужно увеличить/уменьшить указатель.
Ну про память.. ну наверное лучше такой пример:
int var = 100;
int* int_ptr = &var; /* Проинициализированный указатель. Указывает на лок. переменную var. */
int* empty_int_ptr = NULL; /* Проинициализированный указатель, но правда указывающий вникуда.*/
if (NULL == (empty_int_ptr = (int*)malloc(sizeof(int)))) {
fprintf(stderr, "Can`t allocate memory.\n");
return 0;
}
else {
/* Теперь empty_int_ptr указывает на выделенный участок в памяти для нашего процесса,
куда можно что-нибудь записать размером в sizeof(int).*/
*empty_int_ptr = 25;
/* Освобождаем ранее выделенную память */
free(empty_int_ptr);
}
Где-то так.