Видео ролики бесплатно онлайн

Смотреть анал видео

Официальный сайт maxceiling 24/7/365

Смотреть видео бесплатно

DobrijZmej 23.11.2010 18:57

CodingЯзык C и указатели

Про указатели в C уже говорено-переговорено. Но недавно они мне пригодились.
Хочу рассказать новичкам, как именно пригодились.
Долгое время я путался (да и сейчас путаюсь) в этим порождении C - указатель.
Но, на этих выходных, я нашел очень удобное средство его применения. На самом деле, я думаю этих средств намного больше, но это пока что в первый раз где это мне пригодилось. Правда, и полноценно изучать С я только начал.
Итак, где-же мне это пригодилось ?


Для начала немного теории от digiwhite
Указатель представляет собой место, куда может быть записан адрес памяти.
Указатель у современных архитектур и компиляторов может содержать 2^32 значение, т.е. имеет размерность 4 байта (на 64-х разрядных архитектурах - вероятно это будет 8 байт - т.е возможно записать 2^64 значений).
Над указателем можно выполнять операцию разыменовывания указателя '*' и взятия адреса '&'. Пример:

1
2
3
4
5
6
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);


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

Продолжаем практику =)
У меня есть функция, которая в зависимости от результата возвращает правду или ложь.
Но, кроме того, она перемещается по переданному ей файлу, и прикольно было-бы, если бы она возвращала текст, который читает из этого файла.
Итак, теперь поэтапно, как я к этому пришел.
Начну с малого. Давайте разберемся с указателями.

1
int i = 5;


Это еще не указатель. Это целочисленная переменная, расположенная где-то в памяти (в данном конкретном случае - в стеке), и имеющая значение 5.
Для простоты примера, скажем, что она находиться в памяти по адресу 0x0050.
Т.е. в памяти по адресу 0х0050 находится число 5.
Теперь мы хотим передать эту переменную в функцию, к примеру так:

1
printf("%d\n", i);


При входе в функцию создается локальная (в области видимости) копия стека, куда повторно заноситься значение переменной i по новому адресу.
Если внутри фунции printf переменная i будет как-то меняться (ну предположим), то на стек основной программы она никак не повлияет, и как бы она внутри функции не менялась - в основной программе переменная i не измениться.
Нужно это как-то проверить. Напишем исходник.
1) Введем переменную, и посмотрим на каком она адресе:
Создаем файлик main.c с содержимым:
1
2
3
4
5
6
#include <stdio.h> /* этот заголовок понадобится для функции printf    */
int main(){
int i=3; /* создаем переменную i и присваиваем ей значение 3 */
printf("%d\n", i); /* печатаем ее на стандартный вывод ( на экран) */
printf("%p\n", &i;); /* и последнее - распечатаем адрес переменной */
}


Для компиляции я использовал команду:

1
gcc main.c


После того, как она отработала, в каталоге с файлом main.c у меня появился исполняемый файл a.out. Его и запускаем на выполнение командой:

1
./a.out


Результатом его работы будет 2 строчки:
1
2
3
0xBFC0AECC


Первая строка - это значение переменной i.
Вторая строка - это ее адрес в памяти. Вот это выражение "&i" уже и есть указатель на адрес размещения переменной i в памяти стэка.
2) Попробуем теперь эту переменную передать в функцию, и распечатать ее уже оттуда:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdtio.h> /* этот заголовок понадобиться для функции printf */
void t(int); /* прототип функции. */

int main(){
int i=3; /* создаем переменную i и присваиваем ей значение 3 */
t(i); /* Вызываем нашу функцию, и передаем в нее переменную */
printf("%d\n", i); /* печатаем ее на стандартный вывод ( на экран) */
printf("%p\n", &i;); /* и последнее - распечатаем адрес переменной */
}

void t(int m){ /* наша функция, в которой мы делаем те-же вещи, */
/* но уже с внутренней переменной */
printf("m=%d\n", m); /* печатаем значение переменной m */
printf("&m;=%p\n", &m;); /* и адрес в памяти внутренней переменной m */
}


Компилируем еще раз, запускаем, и получаем следующий вывод:
1
2
3
4
m=3
&m;=0xBFEB52B0
i=3
&i;=0xBFEB52CC


Как видим, новая переменная находится совершенно по другому адресу.

3) Давайте попробуем изменить переменную внутри нашей процедуры:
1
2
3
4
5
void t(int m){
m=4; /* меняем значение переменной с тройки на четверку */
printf("m=%d\n", m); /* печатаем значение переменной m */
printf("&m;=%p\n", &m;); /* и адрес в памяти внутренней переменной m */
}


Запускаем, смотрим результат:
1
2
3
4
m=4
&m;=0xBFF674C0
i=3
&i;=0xBFF674DC


Т.е. внутри функции мы переменную изменили, но в главной процедуре значение i не поменялось. Как же нам изменить эту переменную изнутри функции ? Вот тут на помощь нам могут прийти указатели.
4) Мы можем передать в функцию не саму переменную, а адрес в памяти, где она будет находиться. И тогда функция будет работать с этим адресом, а не со своей копией переменной.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
void t(int*); /* функция теперь принимает указатель на переменную */

int main(){
int i=3;
t(&i;); /* и мы передаем указатель на адрес, а не саму переменную*/
printf("i=%d\n", i);
printf("&i;=%p\n", &i;);
}

void t(int* m){
*m=4; /* мы получили адрес, чтобы разименовать указатель,*/
/* ставим перед ним звездочку */
printf("m=%d\n", *m); /* та-же ситуация */
printf("&m;=%p\n", m); /* а вот адрес никак приобразовывать уже не нужно, */
/* в переменной m у нас и так уже адрес переменной */
}



Компилируем, запускаем и получаем результат:
1
2
3
4
m=4
&m;=0xBFFA5B8C
i=4
&i;=0xBFFA5B8C


Как раз то, что от нас и требовалось. Мы изменили переменную во внутренней функции (через указатель на нее), и она изменилась в главной процедуре. Здесь главное не запутаться в переменных, а-то очень просто получить access violation доступа к памяти.

PS: Ну, и в конце, если дозволено будет КА, немного самопиара =) Мысли Доброго Змея: Язык C и указатели
Еще планирую на днях написать статью по поводу указателях в массивах char. Указатель на строку в С сам по себе является указателем, и его не так просто изменить из вызываемой функции.
И еще, если кого заинтересует тема - объясните, плз, когда следует выделять переменным память, а когда достаточно просто размещать их в стэке.


Тэги: pointer указатели
+ 8 -
Похожие Поделиться

digiwhite 23.11.2010 19:56 #
+ 9 -
g++ main.cpp

Если речь идет о языке C, то:
  1. Исходники сохраняем в файлы с расширением *.c
  2. Вызов компилятора - gcc
И еще, если кого заинтересует тема - объясните, плз, когда следует выделять переменным память, а когда достаточно просто размещать их в стэке.


Ну, по указателям следует сказать следующее:
  1. Указатель представляет собой место, куда может быть записан адрес памяти.
  2. Указатель у современных архитектур и компиляторов может содержать 2^32 значение, т.е. имеет размерность 4 байта (на 64-х разрядных архитектурах - вероятно это будет 8 байт - т.е возможно записать 2^64 значений).

  3. Над указателем можно выполнять операцию разыменовывания указателя '*' и взятия адреса '&'. Пример:

    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);



  4. Над указателями можно выполнять арифметические операции, такие как: сложение, вычитание, инкремент и декремент. Однако, тут есть зависимость от того, на какой тип данных указывает указатель. Т.е. операции над указателем переведу к изменению адреса на величину типа данных, на который указывает указатель, умноженное на число, на которое нужно увеличить/уменьшить указатель.



Ну про память.. ну наверное лучше такой пример:

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);
}


Где-то так.
DobrijZmej 24.11.2010 08:56 #
+ 0 -
Спасибо, хорошее дополнение к указателям.
По поводу 4-го пункта хочу эту тему (во всяком случае инкремент и декремент) освятить во второй части: работа с char*
Про память: да, понятно, что используется malloc в размере типа, на который указывает указатель.
Но вопрос у меня несколько в ином: есть ли какие правила размещения переменных в стеке, или в выделенной памяти.
Ну, к примеру, некие большие массивы - понятно, что лучше выделять для них память. Но есть ли еще какие рекомендации ?
digiwhite 24.11.2010 10:17 #
+ 0 -
Ну, чего-то каких-то рекомендаций в память не приходит. Здравый смысл если только :).

По идее чем меньше локальных переменных и параметров у функции, тем лучше, т.к. перед вызовом функции надо все параметры запихнуть в стек, все локальные переменные так же поместить в стек, потом вызвать функцию, по завершению работы надо все это дело из стека убрать. По идее, это по одной команде ассемблера на параметр, на локальную переменную и затем вызов функции. Потом в обратном порядке (только без вызова функции). Но это все приблизительно и конкретными фактами не подкреплено. Скорее теория :). Так-что то, что я сказал, надо еще 10 раз проверить.
f_evgeny 24.11.2010 10:49 #
+ 4 -
Поправки:
Перед вызовом функции нужно поместить в стек только аргументы. Место для всех локальных переменных выделяется одной операцией, сдвигом указателя стека на сумму указателей на аргументы и локальные переменные.
При возврате из функции место освобождается одной операцией - изменением указателя стека на нужную величину величину.
Это все недорогие операции. ИМХО, операции на куче могут быть дороже, особенно когда память фрагментирована. Но тут я не очень уверен.
DobrijZmej 24.11.2010 10:55 #
+ 0 -
хм... ну вообще да, если для каждой переменной выделять память, и не забывать ее освобождать, то это может оказаться дороже.
Спасибо за обсуждение, лучше начал понимать физику процесса =)
digiwhite 24.11.2010 11:25 #
+ 0 -

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


Спасибо, тоже буду лучше знать :). Отсюда в общем-то следует, почему не стоить делать функции с кучей параметров. Лучше по возможности объединить это дело в структуры и передавать по указателю.


Это все недорогие операции. ИМХО, операции на куче могут быть дороже, особенно когда память фрагментирована. Но тут я не очень уверен.

Думаю, что очень многое тут зависит от алгоритма выделения памяти и его реализации. Ну и еще так же от способа работы с памятью (ну т.е выделяем ли мы по 1 байту сто раз, или же выделили 1 раз 100 байт и работаем с этим куском как нам нужно).
f_evgeny 24.11.2010 13:58 #
+ 0 -
> Отсюда в общем-то следует, почему не стоить делать функции с кучей параметров.
> Лучше по возможности объединить это дело в структуры и передавать по указателю.

Я бы не стал делать уж прямо такой жесткий вывод. Тут мы имеем противоречие между быстродействием и прозрачной и логичной архитектурой программы. В 99% случаев, если пишем на Си, быстродействия хватает. А вот прозрачности часто - нет.
Поэтому, я считаю, что передавать в функцию переменную по указателю нужно только в обоснованном случае и быстродействие среди причин тут на последнем месте. Я вижу такие обоснованные причины:
- Нужно обработать сложную структуру данных.
- Нужно вернуть из функции несколько независимых результатов.
Ну и наверняка я не все знаю.
digiwhite 24.11.2010 14:09 #
+ 1 -

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

Забыли про строки.

По поводу возможности объединения в структуры, я имел в виду то, что объединять нужно то, что логически этому способствует.
f_evgeny 24.11.2010 14:30 #
+ 0 -
Забыли про строки.


Согласен, строки тоже нужно упомянуть.
DobrijZmej 24.11.2010 09:28 #
+ 0 -
Если речь идет о языке C, то:

Спасибо, поправил.
Можно ли текст Вашего коммента включить в статью ?
digiwhite 24.11.2010 10:04 #
+ 0 -
Можно конечно :).
f_evgeny 23.11.2010 21:38 #
+ 0 -
> И еще, если кого заинтересует тема - объясните, плз, когда следует выделять переменным
> память, а когда достаточно просто размещать их в стэке.

Не понял вопрос. Для простых программ самому думать об этом не следует. Этим занимается компилятор. Насколько я ничего не помню:

Статические и глобальные переменные компилятор размещает в памяти, автоматические и параметры функций в стеке.
Статические - объявляются внутри функций, с модификатором static. Глобальные - вне функций.
Автоматические объявляются внутри функций (без модификатора static).
cyrus 23.11.2010 22:17 #
+ 3 -
Советую просто прочитать Керниган, Ритчи "Язык программирования Си" - все вопросы сами собой отпадут ;)
f_evgeny 23.11.2010 22:21 #
+ 1 -
Для того, чтобы хорошенько разобраться, полезно все-таки, кроме чтения еще и обсудить с друзьями.
DobrijZmej 24.11.2010 08:53 #
+ 0 -
спасибо, обязательно прочту
kim 04.01.2011 20:25 #
+ 0 -
"Язык С и указатели" звучит как-то странно. Имею в виду, что вся мощь и смысл языка С лежит в этих указателях, ну еще '=' сделали операцией, а не оператором.
Ни одна программа, имею в виду более менее что-то делающая, не обходиться без указателей. Взять к примеру знаменитую "hello, world!", да-да! даже там есть указатель!
И собственно отвечу на вопрос: Динамической памятью, то бишь функциями malloc, realloc и free, стоит пользоваться когда заранее размер входных данных не известен. Это конечно не критерий, но для начала думаю сойдет.

Смотреть видео онлайн

Онлайн видео бесплатно


Смотреть русское с разговорами видео

Online video HD

Видео скачать на телефон

Русские фильмы бесплатно

Full HD video online

Смотреть видео онлайн

Смотреть HD видео бесплатно

School смотреть онлайн