Почему указатели в C важны: как передача по указателю позволяет изменять реальные данные

4

Большинство разработчиков сталкиваются с указателями в C через специфический механизм, известный как передача по указателю. Это не просто синтаксическая особенность; это фундаментальная причина, по которой функции могут изменять внешнее состояние. Вы, вероятно, видели это раньше, не осознавая этого. Рассмотрим функцию scanf. Каждый раз, когда вы вызываете scanf, вы обязаны добавлять оператор взятия адреса (& ) перед своими переменными.

scanf("%d", &myNumber);

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

Неудача передачи по значению

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

Вот наивная реализация, которая не работает:

Запустите этот код. Вывод покажет 5 10, за которым последует 5 10. Обмен не произошел.

Причина проста. В C аргументы по умолчанию передаются по значению. Когда вызывается swap(a, b), система создает локальные копии a и b, названные i и j. Функция отлично меняет местами значения i и j. Но когда функция завершает работу, эти локальные переменные уничтожаются. Оригинальные a и b в main остаются без изменений.

Решение: передача по указателю

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

Вот исправленная реализация:

Теперь вывод будет 5 10, за которым последует 10 5. Обмен сработал.

Как работает разыменование указателя

Разница заключается в том, что передается в функцию.

  1. Вызов: swap(&a, &b) передает адреса a и b.
  2. Параметры: Подпись функции void swap(int *i, int *j) объявляет i и j как указатели на целые числа.
  3. Связь: Внутри swap i содержит адрес a. j содержит адрес b.
  4. Разыменование: Оператор *i = *j означает «перейти по адресу памяти, хранящемуся в i, и записать в него значение, найденное по адресу памяти, хранящемуся в j ».

Визуально представьте две коробки, подписанные a и b.
a содержит 5.
b содержит 10.
i — это записка с адресом коробки a, написанным на ней.
j — это записка с адресом коробки b, написанным на ней.

Когда код выполняет t = *i, он читает записку i, идет по этому адресу и считывает значение 5. Он сохраняет 5 в t. Затем *i = *j считывает значение из коробки b (которое равно 10 ) и записывает его обратно в коробку a. Исходные коробки изменились.

Опасность пропуска адреса