вторник, 23 ноября 2010 г.

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

Долгое время я путался (да и сейчас путаюсь) в этим порождении C - указатель.
Но, на этих выходных, я нашел очень удобное средство его применения. На самом деле, я думаю этих средств намного больше, но это пока что в первый раз где это мне пригодилось. Правда, и полноценно изучать С я только начал.
Итак, где-же мне это пригодилось ?
У меня есть функция, которая в зависимости от результата возвращает правду или ложь.
Но, кроме того, она перемещается по переданному ей файлу, и прикольно было-бы, если бы она возвращала текст, который читает из этого файла.
Итак, теперь поэтапно, как я к этому пришел.
Начну с малого. Давайте разберемся с указателями.
   int i = 5;
Это еще не указатель. Это целочисленная переменная, расположенная где-то в памяти (в данном конкретном случае - в стеке), и имеющая значение 5.
Для простоты примера, скажем, что она находиться в памяти по адресу 0x0050.
Т.е. в памяти по адресу 0х0050 находится число 5.
Теперь мы хотим передать эту переменную в функцию, к примеру так:
   printf("%d\n", i);
При входе в функцию создается локальная (в области видимости) копия стека, куда повторно заноситься значение переменной i по новому адресу.
Если внутри фунции printf переменная i будет как-то меняться (ну предположим), то на стек основной программы она никак не повлияет, и как бы она внутри функции не менялась - в основной программе переменная i не измениться.
Нужно это как-то проверить. Напишем исходник.
1) Введем переменную, и посмотрим на каком она адресе:
Создаем файлик main.cpp с содержимым:
   #include /* этот заголовок понадобится для функции printf */
   int main(){
   int i=3; /* создаем переменную i и присваиваем ей значение 3 */
   printf("%d\n", i); /* печатаем ее на стандартный вывод ( на экран) */
   printf("%p\n", &i); /* и последнее - распечатаем адрес переменной */
   }

Для компиляции я использовал команду:
   g++ main.cpp
После того, как она отработала, в каталоге с файлом main.cpp у меня появился исполняемый файл a.out. Его и запускаем на выполнение командой:
   ./a.out
Результатом его работы будет 2 строчки:
   3
   0xBFC0AECC

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

   #include /* этот заголовок понадобиться для функции 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 */
   }

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

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

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

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

   #include
   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 у нас и так уже адрес переменной */
   }


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

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