Урок №99. Передача по адресу

  Юрий  | 

  |

  Обновл. 18 Ноя 2022  | 

 80021

 ǀ   20 

Есть еще один способ передачи переменных в функцию в языке C++ — по адресу.

Передача по адресу

Передача аргументов по адресу — это передача адреса переменной-аргумента (а не значения исходной переменной). Поскольку аргумент является адресом, то параметром функции должен быть указатель. Затем функция сможет разыменовать этот указатель для доступа или изменения исходного значения. Вот пример функции, которая принимает параметр, передаваемый по адресу:

Результат выполнения программы:

value = 4
value = 7

Как вы можете видеть, функция boo() изменила значение аргумента (переменную value) через параметр-указатель ptr. Передачу по адресу обычно используют с указателями на обычные массивы. Например, следующая функция выведет все значения массива:

Вот пример программы, которая вызывает эту функцию:

Результат:

9 8 6 4 3 2 1

Помните, что фиксированные массивы распадаются в указатели при передаче в функцию, поэтому их длину нужно передавать в виде отдельного параметра. Перед разыменованием параметров, передаваемых по адресу, не лишним будет проверить — не являются ли они нулевыми указателями. Разыменование нулевого указателя приведет к сбою в программе. Вот функция printArray() с проверкой (обнаружением) нулевых указателей:

Передача по константному адресу


Поскольку printArray() все равно не изменяет значения получаемых аргументов, то хорошей идеей будет сделать параметр array константным:

Так мы видим сразу, что printArray() не изменит переданный аргумент array. Когда вы передаете указатель в функцию по адресу, то значение этого указателя (адрес, на который он указывает) копируется из аргумента в параметр функции. Другими словами, он передается по значению! Если изменить значение параметра функции, то изменится только копия, исходный указатель-аргумент не будет изменен. Например:

В tempPtr копируется адрес указателя ptr. Несмотря на то, что мы изменили tempPtr на нулевой указатель (присвоили ему nullptr), это никак не повлияло на значение, на которое указывает ptr. Следовательно, результат выполнения программы:

6
6

Обратите внимание, хотя сам адрес передается по значению, вы все равно можете разыменовать его для изменения значения исходного аргумента. Запутано? Давайте проясним:

   При передаче аргумента по адресу в переменную-параметр функции копируется адрес из аргумента. В этот момент параметр функции и аргумент указывают на одно и то же значение.

   Если параметр функции затем разыменовать для изменения исходного значения, то это приведет к изменению значения, на которое указывает аргумент, поскольку параметр функции и аргумент указывают на одно и то же значение!

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

В следующей программе это всё хорошо проиллюстрировано:

Результат выполнения программы:

6
7

Передача адресов по ссылке

Следует вопрос: «А что, если мы хотим изменить адрес, на который указывает аргумент, внутри функции?». Оказывается, это можно сделать очень легко. Вы можете просто передать адрес по ссылке. Синтаксис ссылки на указатель может показаться немного странным, но все же:

Результат выполнения программы:

6 ptr is null

Наконец, наша функция setToNull() действительно изменила значение ptr с &six на nullptr!

Существует только передача по значению


Теперь, когда вы понимаете основные различия между передачей по ссылке, по адресу и по значению, давайте немного поговорим о том, что находится «под капотом».

На уроке о ссылках мы упоминали, что ссылки на самом деле реализуются с помощью указателей. Это означает, что передача по ссылке является просто передачей по адресу. И чуть выше мы говорили, что передача по адресу на самом деле является передачей адреса по значению! Из этого следует, что C++ действительно передает всё по значению!

Плюсы и минусы передачи по адресу

Плюсы передачи по адресу:

   Передача по адресу позволяет функции изменить значение аргумента, что иногда полезно. В противном случае, используем const для гарантии того, что функция не изменит аргумент.

   Поскольку копирования аргументов не происходит, то скорость передачи по адресу достаточно высокая, даже если передавать большие структуры или классы.

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

Минусы передачи по адресу:

   Все указатели нужно проверять, не являются ли они нулевыми. Попытка разыменовать нулевой указатель приведет к сбою в программе.

   Поскольку разыменование указателя выполняется медленнее, чем доступ к значению напрямую, то доступ к аргументам, переданным по адресу, выполняется также медленнее, чем доступ к аргументам, переданным по значению.

Когда использовать передачу по адресу:

   при передаче обычных массивов (если нет никаких проблем с тем, что массивы распадаются в указатели при передаче).

Когда не использовать передачу по адресу:

   при передаче структур или классов (используйте передачу по ссылке);

   при передаче фундаментальных типов данных (используйте передачу по значению).

Как вы можете видеть сами, передача по адресу и по ссылке имеют почти одинаковые преимущества и недостатки. Поскольку передача по ссылке обычно безопаснее, чем передача по адресу, то в большинстве случаев предпочтительнее использовать передачу по ссылке.

Правило: Используйте передачу по ссылке, вместо передачи по адресу, когда это возможно.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (336 оценок, среднее: 4,93 из 5)
Загрузка...

Комментариев: 20

  1. Sswe:

    Что то я не понял.

    Когда не использовать передачу по адресу:

    при передаче структур или классов (используйте передачу по ссылке);


    На уроке о ссылках мы упоминали, что ссылки на самом деле реализуются с помощью указателей. Это означает, что передача по ссылке является просто передачей по адресу.

  2. Константин:

    Насколько я понимаю, передавать фиксированный массив по ссылке не имеет смысла, т.к. придётся указывать его размер. Но если уж и передавать указатель на массив, то почему бы явно не передавать указатель на массив, вместо того, чтобы передавать указатель на первый элемент? Тогда не придётся как-то по особенному называть параметр функции, ведь итак всё понятно будет:
    int (*arr)[] — PtrArray_int
    int arr[] / int *arr — Ptr_int

    Почему принято передавать указатель на первый элемент, если в С++ возможно передавать настоящий указатель на массив?

    1. xshady:

      думаю, тут скорее не "как принято", а то как передача массивов в функцию умолчанию так работает в языке, поэтому это и используют, чтобы ничего не усложнять

    2. Сергей:

      Описание int (*arr)[]  заинтересовало. Можно об єтом подробней или пример?

  3. Старый программист:

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

    Это как?

    "разыменование указателя" — это не более чем абстрация языка. Никакого машинного кода не генерируется. Как это может быть быстрее? Не думаю, что прямая адресация хоть как-то быстрее косвенно-регистровой. И та и другая команда выполняется за одинаковое количество тактов. Если смотреть трансляцию на RISС конвеер, то разницы найти не возможно, стадия MEM в обоих случаях выполняется за фиксированный такт.

  4. Алексей:

    Немного запутали, но на практике вопросы уйдут.

  5. Дмитрий:

    Я вот честно говоря тоже запутался как нужно передавать массив. В главе Урок №75 написано, что массив и без амперсанта и без указателя и так передается в функцию без копирования. Здесь написано, что нужно через адрес. При этом структуры и классы лучше через ссылку. Чем массив принципиально отличается от структуры? Не понятно, как правильно

    1. Егор:

      Насколько я понял, фиксированный массив при передаче в функцию без ничего, приходит в неё в виде указателя на первый элемент, (ничего не копируется). Если же ты передаешь std::array или std::vector, то без ничего они полностью скопируются в аргумент функции (что затратно)
      Поэтому лучше использовать адреса. А еще лучше ссылки.

  6. Torgu:

    Так и не понял, зачем передавать массивы по адресу, когда безопаснее по ссылке?

    1. Анастасия:

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

      1. Torgu:

        так в чем собственно преимущество?

        1. Анастасия:

          Даже если передавать по ссылке, все равно массив распадется в указатель. Так в чем преимущество передачи массива по ссылке?

        2. Torgu:

          Здесь ограничение по глубине комментариев, поэтому могу ответить только себе. Итак, вы отвечаете вопросом на вопрос? То есть у указателей все-таки нет преимуществ перед ссылками?

    2. Александр:

      по ссылке на что Вы собираетесь передавать массив?

      Я так понимаю, что немного дальше автор нас будет знакомить с итераторами. Так вот, ссылка для массива — это простой итератор. И передача массива по ссылке унифицирует интерфейсы работы функций с различными контейнерами. Если Вы умеете обрабатывать массив, жонглируя исключительно ссылками (при обращении к элементам пользуетесь разыменованием, а не оператором [] ), то в большинстве случаев можете тот же код применить и к другим контейнерам.

      Кстати, желательно передавать в функцию не "указатель на начало массива + размер", а "указатель на начало и указатель на конец" (точнее, на элемент, который идет после последнего). Поначалу будет казаться, что это ненужное усложнение, но на самом деле так намного проще… да и многие стандартные функции обработки контейнеров принимают именно такой тип аргументов.

      1. releyshic:

        «и указатель на конец» (точнее, на элемент, который идет после последнего).» нихрена вы быкдоров в своём коде понаоставляли…

        рай для хацкеров какойто

  7. Shom:

    Здравствуйте! Извините за тупость, но вот тут:

    мне не совсем понятно array[index].
    Ведь array — это указатель на первый элемент массива, то-есть, фактически, это адрес первого элемента и как к этому адресу пристыкуется [index]?
    При следующих итерациях цикла, по моей логике, должен вновь получится адрес первого элемента, но уже со следующим индексом в квадратных скобках.

    1. Фото аватара Юрий:

      Привет.

      array — это тип указателя на массив (int *), а array[index] — это уже набор значений определенного типа, например, int-ов. Определив array[4] и используя возле array квадратные скобки со значением внутри вы автоматически обращаетесь к array, как к набору int-ов, а не как к указателю на первый элемент. Не указывая квадратных скобок и значения в них вы можете относиться к array как к указателю на массив (на первый элемент).

      Т.е. если вы добавляете [index] к array, то array используется в этом случае как тип данных (int), а не как указатель (int *). И при последующих итерациях цикла у вас получается не адрес первого элемента + следующий индекс в квадратных скобках, а адрес 2-ого значения из набора int-ов (т.е. из массива).

      1. Shom:

        Всё понял, спасибо!
        Но возник ещё один вопрос: память для массива всегда одним целым куском выделяется? Не может быть так, к примеру, что сначала идут первые сто адресов для массива, потом адрес памяти выделен под переменную (массив инициализировали позже переменной), а потом следующие адреса продолжают массив и при инструкции в цикле

        эта переменная попадёт в вывод?

        1. Фото аватара Юрий:

          Насколько я понял ваш вопрос, то память для массивов выделяется либо сразу (фиксированные массивы), либо по мере увеличения количества элементов (динамическое выделение памяти для динамических массивов). Может ли выделение памяти прерываться, как описали вы — не знаю.

        2. Александр:

          при выделении памяти под массив (в том числе и под динамический) адреса элементов ГАРАНТИРОВАНО идут по порядку

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *