Урок №84. Символьные константы строк C-style

  Юрий  | 

  |

  Обновл. 13 Сен 2021  | 

 47392

 ǀ   11 

Из материалов урока №79 мы уже знаем, как создать и инициализировать строку C-style:

Язык C++ поддерживает еще один способ создания символьных констант строк C-style — через указатели:

Хотя обе эти программы работают и выдают одинаковые результаты, выделение памяти в них выполняется по-разному.

В первом случае в программе выделяется память для фиксированного массива длиной 5 и инициализируется эта память строкой John\0. Поскольку память была специально выделена для массива, то мы можем изменять её содержимое. Сам массив рассматривается как обычная локальная переменная, поэтому, когда он выходит из области видимости, память, используемая им, освобождается для других объектов.

Что происходит в случае с символьной константой? Компилятор помещает строку John\0 в память типа read-only (только чтение), а затем создает указатель, который указывает на эту строку. Несколько строковых литералов с одним и тем же содержимым могут указывать на один и тот же адрес. Поскольку эта память доступна только для чтения, а также потому, что внесение изменений в строковый литерал может повлиять на дальнейшее его использование, лучше всего перестраховаться, объявив строку константой (типа const). Также, поскольку строки, объявленные таким образом, существуют на протяжении всей жизни программы (они имеют статическую продолжительность, а не автоматическую, как большинство других локально определенных литералов), нам не нужно беспокоиться о проблемах, связанных с областью видимости. Поэтому следующее в порядке вещей:

В фрагменте, приведенном выше, функция getName() возвращает указатель на строку C-style John. Всё хорошо, так как John не выходит из области видимости, когда getName() завершает свое выполнение, поэтому вызывающий объект всё равно имеет доступ к строке.

std::cout и указатели типа char

На этом этапе вы, возможно, уже успели заметить то, как std::cout обрабатывает указатели разных типов. Рассмотрим следующий пример:

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

0046FAE8
Hello!
John

Почему в массиве типа int выводится адрес, а в массивах типа char — строки?

Дело в том, что при передаче указателя не типа char, в результате выводится просто содержимое этого указателя (адрес памяти). Однако, если вы передадите объект типа char* или const char*, то std::cout предположит, что вы намереваетесь вывести строку. Следовательно, вместо вывода значения указателя — выведется строка, на которую тот указывает!

Хотя это всё замечательно в 99% случаев, но это может привести и к неожиданным результатам, например:

Здесь мы намереваемся вывести адрес переменной a. Тем не менее, &a имеет тип char*, поэтому std::cout выведет это как строку!

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

R╠╠╠╠╜╡4;¿■A

Почему так? std::cout предположил, что &a (типа char*) является строкой. Поэтому сначала вывелось R, а затем вывод продолжился. Следующим в памяти был мусор. В конце концов, std::cout столкнулся с ячейкой памяти, имеющей значение 0, которое он интерпретировал как нуль-терминатор, и, соответственно, прекратил вывод. То, что вы видите в результате, может отличаться, в зависимости от того, что находится в памяти после переменной a.

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


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

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

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

  1. HanGetsu:

    Пару моментов остались непонятны.

    Правильно ли я понимаю, что тип данных который мы возвращаем, мы сделали указатель на строку, и при вызове функции где то создаётся символьный массив, который существует до закрытия программы?
    Ну а так же, всё же интересно, как же тогда вывести адрес символа?

    1. Геннадий:

      Я адрес символа вывожу через printf:

    2. Artyom T:

      1. Григорий:

        Для приведения указателей лучше использовать не const_cast, а reinterpret_cast.

  2. HillBilly:

    Вопрос №1: Если символьный указатель объявлен в параметрах фунции или в теле он тоже имеет статическую продолжительность и существуют на протяжении всей жизни программы или удаляется после блоков{}?
    Пример:

    Вопрос №2: Произдет ли утечка памяти при вызове функции pepe? Или все 3 переменные: name, temp и её возвращаемая копия будут всегда указывать на одни и теже адреса в памяти???

  3. Kris:

    Очень странно по поводу литералов. В смысле, что если мы берем литерал "John", эта штука сэйвится в область глобальных переменных типа read-only на все время работы программы. Значит ли это, что если я пишу так:

    то "John" будет храниться в памяти до конца, т.е., фактически, произойдет утечка памяти? Это звучит нелепо, как минимум. Логичнее было бы, если бы "John" был бы временной областью памяти, которая сама автоматически очищалась бы, как это делают временные результаты выполнения программы. Т.е., лежало бы на стеке. Но эта штука, скорее всего, реально не лежит на стеке, ибо если изменить ее, то произойдет выброс исключения, поэтому она хранится где-то в специальной области памяти. Но кто же очищает за нас память, когда мы меняем указатель? Кто — то же должен очистить память, иначе было бы невозможно без утечки памяти изменить указатель.

    1. Наталья:

      Строковые литералы глобальны. Но глобальная область памяти — это не динамическая память, она очищается по завершении работы программы.

  4. alexk:

    Особенно интересно вот так:

    и результат:
    >>>>>> Console - START >>>>>>
    abcdef
    p = abcdef:���

    abcde
    p = abcde

    1. kmish:

      Мой вывод твоей программы:

      abcdef
      p = a╠╠╠╠CF└·6о.#

      abcde
      p = a╠╠╠╠CF└·6о.#

      1. alexk:

        на MSVS-2010 + Win7 такая прога и у меня кажет так же как и у ВАС … ?! … 🙂 …

        а вот на xubuntu 16.04 при компиляции строкой:
        g++ -Wall -std=c++1y …

        вариант xubuntu, лично мне, БОЛЬШЕ нравится ! … 🙂 …

    2. Наталья:

      Вы создаёте не массив! И нет гарантий, что эти переменные расположены последовательно. Результаты могут отличаться на разных системах.

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

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