Передача параметров по указателю

В Си аргументы всегда передаются единственным образом, часто называемым «передачей по значению». Например, пусть есть функция

 

void foo (int x)
{
  x = 17;
}
 

Мы можем вызывать её любым из следующих способов:

 

int main ()
{
  int z = 5;
  foo (z);
  foo (z + 1);
  foo (125);
}
 

Разумеется, ни один из этих вызовов не изменяет значения переменной z. (Для последних двух вызовов это совершенно естественно, а вот в первом случае вы могли засомневаться.)

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

Перепишем предыдущий пример так:

 

void foo (int *x)
{
  *x = 17;
}
int main ()
{
  int z = 5;
  foo (&z);
  /* остальные варианты больше не имеют смысла, т.к. z теперь равно 17 */
}
 

Некоторые разработчики предпочитают наличие амперсанда в f(&obj) как подсказки о том, что значение obj может измениться внутри вызова. Правда, если obj уже и так указатель, то амперсанд не будет нужен.

Плюсы передачи по указателю

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

Это экономия одновременно и по памяти, и по скорости!

Как быть, если мы хотим этой экономии и решили передавать объект по адресу, но не хотим дать функции потенциальную возможность его (случайного) изменения?

Можно использовать константный указатель: const T*

Минусы передачи по указателю

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

warning: passing argument 1 of 'foo' makes pointer from integer without a cast

Представьте себе, что foo довольно длинна, и везде, где употребляется x, нужна звёздочка, а вызывается foo 48 раз в разных местах программы — при этом иногда нужен амперсанд, иногда нет.

Итак, легко забыть звёздочку в теле функции foo или амперсанд — в её вызове, значит переданное число может случайно истолковаться как адрес числа. Обращение по непредсказуемому адресу приводит к ошибке Segmentation fault и завершению программы.

Поэтому передача аргументов по адресу опасна!