Как посмотреть код возврата си

Обновлено: 07.07.2024

С помощью отладчика Visual Studio можно переходить по коду для проверки состояния приложения и просмотра последовательности выполнения. Для быстрого перехода к коду, который необходимо изучить, можно использовать сочетания клавиш, команды отладки, точки останова и другие функции. Если вы знакомы с командами и сочетаниями клавиш для навигации по отладчику, вы можете быстро и с легкостью находить и устранять проблемы в работе приложений.

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

В режиме приостановки выполнения выполнение приложения приостанавливается, но функции, переменные и объекты при этом остаются в памяти. Когда отладчик находится в режиме приостановки выполнения, можно перемещаться по коду. Существует два стандартных способа быстрого перехода в режим приостановки выполнения:

Начните пошаговое выполнение кода, нажав клавишу F10 или F11. Это позволит быстро найти точку входа приложения. После этого можно продолжать нажимать команды перехода для перемещения по коду.

Например, в редакторе кода в Visual Studio можно использовать команду Выполнить до текущей позиции, чтобы запустить приложение, присоединить отладчик и перейти в режим приостановки выполнения, а затем нажать клавишу F11 для перехода по коду:

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

Большинство окон отладчика, таких как Модули и Контрольные значения, доступны только тогда, когда отладчик присоединен к приложению. Некоторые возможности отладчика, такие как просмотр значений переменных в окне локальных переменных или вычисление выражений в окне контрольных значений, доступны только при приостановке отладчика (то есть в режиме приостановки выполнения).

Если во время приостановки выполнения кода не загружены исходные файлы или файлы символов (PDB), отладчик отображает страницу Исходный файл не найден или Символы не найдены, которая поможет найти и загрузить файлы. См. статью Указание файлов символов (.pdb) и файлов с исходным кодом в отладчике Visual Studio. Если вы не можете загрузить исходные файлы или файлы символов, можно выполнить отладку инструкций на языке ассемблера в окне Дизассемблирование.

Пошаговое прохождение кода

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

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

Для остановки выполнения на каждом операторе во время отладки используйте команду Отладка > Шаг с заходом или нажмите клавишу F11.

Отладчик осуществляет пошаговое выполнение операторов кода, а не физических строк. Например, предложение if может быть записано в одной строке:

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

При вызове вложенных функций команда Шаг с заходом позволяет попасть в самую глубокую вложенную функцию. Например, если использовать Шаг с заходом на вызове Func1(Func2()) , отладчик заходит в функцию Func2 .

При выполнении каждой строки кода можно наводить указатель мыши на переменные, чтобы просматривать их значения, или использовать окна Локальные переменные и Контрольные значения для наблюдения за изменением значений. Кроме того, можно визуально отслеживать стек вызовов при выполнении шагов с заходом в функции. (Сведения, касающиеся только Visual Studio Enterprise, см. в статье Сопоставление методов в визуализации стека вызовов при отладке.)

Пошаговое прохождение кода и пропуск некоторых функций

При отладке можно пропустить функцию. Или, возможно, вы знаете, что некоторый код работает, например, хорошо протестированный код библиотеки. Чтобы пропустить код во время пошагового выполнения, можно использовать приведенные ниже команды. Функции по-прежнему выполняются, но отладчик пропускает их.

Выполнение до указанного места или функции

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

Выполнение до точки останова в коде

Чтобы задать простую точку останова в коде, щелкните в левом поле напротив строки кода, в которой нужно приостановить выполнение. Можно также выбрать строку и нажать клавишу F9, выбрать команду Отладка > Переключить точку останова или щелкнуть правой кнопкой мыши и выбрать команду Точка останова > Вставить точку останова. Точка останова отображается как красный кружок в левом поле рядом со строкой кода. Отладчик приостанавливает выполнение непосредственно перед выполнением строки.

Снимок экрана: задание точки останова.

Точки останова в Visual Studio предоставляют широкий набор функций, таких как условные точки останова и точки трассировки. Дополнительные сведения см. в статье Использование точек останова.

Выполнение до точки останова функции

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

Указание точки останова функции по имени

Выберите команду Отладка > Создать точку останова > Точка останова функции.

В диалоговом окне Новая точка останова функции введите имя функции и выберите ее язык:

Снимок экрана: диалоговое окно создания точки останова функции.

Щелкните ОК.

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

Снимок экрана: перегруженные точки останова функции.

Выбор точки останова функции из стека вызовов

Во время отладки откройте окно Стек вызовов, выбрав пункт Отладка > Окна > Стек вызовов.

В окне Стек вызовов щелкните правой кнопкой мыши имя функции и выберите команду Выполнить до текущей позиции или нажмите клавиши CTRL+F10.

Сведения о визуальном отслеживании стека вызовов см. в статье Сопоставление методов в визуализации стека вызовов при отладке.

Выполнение до расположения курсора

Чтобы выполнить код до позиции курсора, в окне исходного кода или в окне Стек вызовов выберите строку, в которой нужно прервать выполнение, а затем щелкните ее правой кнопкой мыши и выберите команду Выполнить до текущей позиции или нажмите клавиши CTRL+F10. Выбор команды Выполнить до текущей позиции аналогичен заданию временной точки останова.

Принудительное выполнение до расположения курсора

Чтобы выполнить код до позиции курсора, в окне исходного кода или в окне Стек вызовов выберите строку, в которой нужно прервать выполнение, а затем щелкните ее правой кнопкой мыши и выберите элемент Force Run To Cursor (Принудительное выполнение до расположения курсора). Если выбрать параметр Force Run To Cursor (Принудительное выполнение до расположения курсора), все точки останова и первичные исключения будут пропускаться, пока отладчик не достигнет строки кода, где находится курсор.

Выполнение до щелкнутого

Во время приостановки работы отладчика можно навести указатель мыши на оператор в исходном коде или в окне Дизассемблирование и щелкнуть значок с зеленой стрелкой Выполнить до этого места. Если используется команда Выполнение до щелкнутого позволяет не устанавливать временную точку останова.

Снимок экрана: команда

Команда Выполнение до щелкнутого доступна начиная с версии Visual Studio 2017.

Принудительное выполнение до щелчка

Когда работа отладчика приостановлена, вы можете навести указатель на оператор в исходном коде, удерживая нажатой клавишу SHIFT, и выбрать элемент Принудительное выполнение до текущей позиции (значок с двумя зелеными стрелками). При выборе этого параметра приложение присоединяет отладчик Visual Studio и приостанавливает выполнение в положении курсора. Все точки останова и первичные исключения, обнаруженные во время выполнения, временно отключаются.

Снимок экрана: команда

Параметр Force Run to Click (Принудительное выполнение до отмеченного щелчком расположения) доступен, начиная с версии Visual Studio 2022.

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

Чтобы приостановить выполнение в следующей доступной строке кода в выполняющемся приложении, выберите команду Отладка > Прервать все или нажмите клавиши CTRL+ALT+BREAK.

Перемещение указателя для изменения потока выполнения

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

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

Счетчик программы переходит непосредственно к новому расположению. Инструкции между старой и новой точками не выполняются. Однако при перемещении точки выполнения обратно промежуточные инструкции не отменяются.

Отладка кода, не являющегося пользовательским

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

Для просмотра кода платформы, кода сторонней библиотеки или системных вызовов во время отладки можно отключить функцию "Только мой код". В меню Сервис (или Отладка) выберите пункты Параметры > Отладка и снимите флажок Включить только мой код. Когда функция "Только мой код" отключена, в окнах отладчика отображается код, не являющийся пользовательским, и отладчик может выполнять его по шагам.

Режим "Только мой код" не поддерживается для проектов устройств.

Отладка системного кода

Если вы загрузили отладочные символы для системного кода Майкрософт и отключили режим "Только мой код", можно производить шаг с заходом в системный вызов так же, как в любой другой вызов.

Сведения о загрузке символов Майкрософт см. в разделе Настройка расположения символов и параметров загрузки.

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

Во время отладки откройте окно Модули, выбрав пункт Отладка > Окна > Модули или нажав клавиши CTRL+ALT+U.

Определить, для каких модулей символы загружены, можно по значению в столбце Состояние символов в окне Модули. Щелкните правой кнопкой мыши модуль, для которого требуется загрузить символы, и выберите команду Загрузить символы.

Шаг с заходом в свойства и операторы в управляемом коде

По умолчанию отладчик обходит свойства и операторы при пошаговом выполнении в управляемом коде. В большинстве случаев это повышает удобство и эффективность отладки. Чтобы отключить пошаговое выполнение свойств и операторов, выберите пункт Отладка > Параметры. На странице Отладка > Общие снимите флажок Обход свойств и операторов (только управляемый код) .

  • Код возврата (англ. Exit status) программы, — это целочисленное значение, которое дочерний процесс возвращает родительскому процессу в момент завершения.

Связанные понятия

Атомарная (атом от греч. atomos — неделимое) операция — операция, которая либо выполняется целиком, либо не выполняется вовсе; операция, которая не может быть частично выполнена и частично не выполнена.

Сравнение с обменом (англ. compare and set, compare and swap, CAS) — атомарная инструкция, сравнивающая значение в памяти с одним из аргументов, и в случае успеха записывающая второй аргумент в память. Поддерживается в семействах процессоров x86, Itanium, Sparc и других.

Соглашение о вызове (англ. calling convention) — описание технических особенностей вызова подпрограмм, определяющее.

Структурированная обработка исключений (англ. SEH — Structured Exception Handling) — механизм обработки программных и аппаратных исключений в операционной системе Microsoft Windows, позволяющий программистам контролировать обработку исключений, а также являющийся отладочным средством.

В информатике и теории автоматов состояние цифровой логической схемы или компьютерной программы является техническим термином для всей хранимой информации, к которой схема или программа в данный момент времени имеет доступ. Вывод данных цифровой схемы или компьютерной программы в любой момент времени полностью определяется его текущими входными данными и его состоянием.

Перенаправление ввода-вывода — возможность командной оболочки ряда операционных систем перенаправлять стандартные потоки в определённое пользователем место, например, в файл. Характерна для Unix-подобных операционных систем, но в разной степени реализована и в операционных системах других семейств.

Файловый дескриптор — это неотрицательное целое число. Когда создается новый поток ввода-вывода, ядро возвращает процессу, создавшему поток ввода-вывода, его файловый дескриптор.

В информатике, блокировка — механизм синхронизации, позволяющий обеспечить исключительный доступ к разделяемому ресурсу между несколькими потоками. Блокировки — это один из способов обеспечить политику управления распараллеливанием.

Сте́ковый кадр (фрейм) (англ. stack frame) — механизм передачи аргументов и выделения временной памяти (в процедурах языков программирования высокого уровня) с использованием системного стека; ячейка памяти в стеке.

Динамическая идентификация типа данных (англ. run-time type information, run-time type identification, RTTI) — механизм в некоторых языках программирования, который позволяет определить тип данных переменной или объекта во время выполнения программы.

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

Обработчик прерываний (или процедура обслуживания прерываний) — специальная процедура, вызываемая по прерыванию для выполнения его обработки. Обработчики прерываний могут выполнять множество функций, которые зависят от причины, которая вызвала прерывание.

Кодогенерация — часть процесса компиляции, когда специальная часть компилятора, кодогенератор, конвертирует синтаксически корректную программу в последовательность инструкций, которые могут выполняться на машине. При этом могут применяться различные, в первую очередь машинно-зависимые оптимизации. Часто кодогенератор является общей частью для множества компиляторов. Каждый из них генерирует промежуточный код, который подаётся на вход кодогенератору.

Ошибка сегментации (англ. Segmentation fault, сокр. segfault, жарг. сегфолт) — ошибка программного обеспечения, возникающая при попытке обращения к недоступным для записи участкам памяти либо при попытке изменить память запрещённым способом. В системах на основе процессоров Motorola 68000 эти ошибки, как правило, известны как ошибки адреса или шины.

В языках программирования объявле́ние (англ. declaration) включает в себя указание идентификатора, типа, а также других аспектов элементов языка, например, переменных и функций. Объявление используется, чтобы уведомить компилятор о существовании элемента; это весьма важно для многих языков (например, таких как Си), требующих объявления переменных перед их использованием.

Счётчик кома́нд (также PC = program counter, IP = instruction pointer, IAR = instruction address register, СЧАК = счётчик адресуемых команд) — регистр процессора, который указывает, какую команду нужно выполнять следующей.

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

Динамическое распределение памяти — способ выделения оперативной памяти компьютера для объектов в программе, при котором выделение памяти под объект осуществляется во время выполнения программы.

Разделяемая память (англ. Shared memory) является самым быстрым средством обмена данными между процессами.

Самомодифицирующийся код (СМК) — программный приём, при котором приложение создаёт или изменяет часть своего программного кода во время выполнения. Такой код обычно применяют в программах, написанных под процессор с фон-неймановской организацией памяти.

Контро́льная су́мма — некоторое значение, рассчитанное по набору данных путём применения определённого алгоритма и используемое для проверки целостности данных при их передаче или хранении. Также контрольные суммы могут использоваться для быстрого сравнения двух наборов данных на неэквивалентность: с большой вероятностью различные наборы данных будут иметь неравные контрольные суммы. Это может быть использовано, например, для обнаружения компьютерных вирусов. Несмотря на своё название, контрольная.

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

Реентерабельность тесно связана с безопасностью функции в многопоточной среде (thread-safety), тем не менее, это разные понятия. Обеспечение реентерабельности является ключевым моментом при программировании многозадачных систем, в частности, операционных систем.

Неопределённое поведение (англ. undefined behaviour, в ряде источников непредсказуемое поведение) — свойство некоторых языков программирования (наиболее заметно в Си), программных библиотек и аппаратного обеспечения в определённых маргинальных ситуациях выдавать результат, зависящий от реализации компилятора (библиотеки, микросхемы) и случайных факторов наподобие состояния памяти или сработавшего прерывания. Другими словами, спецификация не определяет поведение языка (библиотеки, микросхемы) в любых.

Защита памяти (англ. Memory protection) — это способ управления правами доступа к отдельным регионам памяти. Используется большинством многозадачных операционных систем. Основной целью защиты памяти является запрет доступа процессу к той памяти, которая не выделена для этого процесса. Такие запреты повышают надёжность работы как программ, так и операционных систем, так как ошибка в одной программе не может повлиять непосредственно на память других приложений. Следует различать общий принцип защиты.

В императивном программировании порядок выполнения (порядок исполнения, порядок вычислений) — это способ упорядочения инструкций программы в процессе её выполнения.

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

Сокеты Беркли — интерфейс программирования приложений (API), представляющий собой библиотеку для разработки приложений на языке C с поддержкой межпроцессного взаимодействия (IPC), часто применяемый в компьютерных сетях.

Механизм копирования при записи (англ. Copy-On-Write, COW) используется для оптимизации многих процессов, происходящих в операционной системе, таких как, например, работа с оперативной памятью или файлами на диске (пример — ext3cow).

Систе́мный вы́зов (англ. system call) в программировании и вычислительной технике — обращение прикладной программы к ядру операционной системы для выполнения какой-либо операции.

В компьютерных технологиях, программная транзакционная память (англ. software transactional memory, SТМ) представляет собой механизм управления параллелизмом, аналогичный механизму транзакций баз данных для управления доступом к совместно используемой памяти в параллельных вычислениях. Это альтернатива для синхронизации на основе блокировки. Транзакция в этом контексте является частью кода, который выполняет считывание и запись в разделяемую (совместно используемую) память. Считывание и запись логически.

А́дресное пространство (англ. address space) — совокупность всех допустимых адресов каких-либо объектов вычислительной системы — ячеек памяти, секторов диска, узлов сети и т. п., которые могут быть использованы для доступа к этим объектам при определенном режиме работы (состоянии системы).

Взаи́мная блокиро́вка (англ. deadlock) — ситуация в многозадачной среде или СУБД, при которой несколько процессов находятся в состоянии ожидания ресурсов, занятых друг другом, и ни один из них не может продолжать свое выполнение.

И́мя фа́йла — строка символов, однозначно определяющая файл в некотором пространстве имён файловой системы (ФС), обычно называемом каталогом, директорией или папкой. Имена файлов строятся по правилам, принятым в той или иной файловой и операционной системах (ОС). Многие системы позволяют назначать имена как обычным файлам, так и каталогам и специальным объектам (символическим ссылкам, блочным устройствам и т. п.).

Язык программирования Си поддерживает множество функций стандартных библиотек для файлового ввода и вывода. Эти функции составляют основу заголовочного файла стандартной библиотеки языка Си .

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

Таблица виртуальных методов (англ. virtual method table, VMT) — координирующая таблица или vtable — механизм, используемый в языках программирования для поддержки динамического соответствия (или метода позднего связывания).

Кома́нда — это указание компьютерной программе действовать как некий интерпретатор для решения задачи. В более общем случае, команда — это указание некоему интерфейсу командной строки, такому как shell.

Планирование выполнения задач — одна из ключевых концепций в многозадачности и многопроцессорности как в операционных системах общего назначения, так и в операционных системах реального времени. Планирование заключается в назначении приоритетов процессам в очереди с приоритетами. Программный код, выполняющий эту задачу, называется планировщиком (англ. task switcher, scheduler).

Вариативный шаблон или шаблон с переменным числом аргументов в программировании — шаблон с заранее неизвестным числом аргументов, которые формируют один или несколько так называемых пакетов параметров.

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

Как оказалось, возврат вызывающему значений из функции по значению, по адресу или по ссылке работает почти так же, как передача аргументов в функцию. У каждого метода присутствуют все те же плюсы и минусы. Основное различие между ними состоит в том, что направление потока данных меняется на противоположное. Однако есть еще одна дополнительная сложность – поскольку локальные переменные в функции выходят за пределы области видимости и уничтожаются при возврате из функции, нам необходимо учитывать влияние этого на каждый тип возврата.

Возврат по значению

Возврат по значению – это самый простой и безопасный тип возврата значения. Когда значение возвращается по значению, вызывающему возвращается копия этого значения. Как и в случае передачи по значению, вы можете возвращать значения литералов (например, 5), переменных (например, x ) или выражений (например, x + 1 ), что делает возврат по значению очень гибким.

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

Возврат по значению является наиболее подходящим при возврате переменных, которые были объявлены внутри функции, или для возврата аргументов функции, переданных по значению. Однако, как и передача по значению, возврат по значению для структур и больших классов выполняется медленно.

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

  • При возврате переменных, которые были объявлены внутри функции
  • При возврате аргументов функции, переданных по значению

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

  • При возврате встроенного массива или указателя (используйте возврат по адресу)
  • При возврате большой структуры или класса (используйте возврат по ссылке)

Возврат по адресу

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

Однако возврат по адресу имеет один дополнительный недостаток, которого нет при возврате по значению – если вы попытаетесь вернуть адрес переменной, локальной для функции, ваша программа будет демонстрировать неопределенное поведение. Рассмотрим следующий пример:

Как вы можете видеть здесь, value уничтожается сразу после того, как его адрес возвращается вызывающему. Конечным результатом является то, что вызывающий получает адрес невыделенной памяти (висячий указатель), что вызовет проблемы при использовании. Это частая ошибка, которую допускают начинающие программисты. Многие более новые компиляторы выдают предупреждение (а не ошибку), если программист пытается вернуть локальную переменную по адресу – однако есть довольно много способов обмануть компилятор, чтобы он позволил вам сделать что-то нелегальное без генерации предупреждения, поэтому эта ответственность лежит на программисте, чтобы гарантировать, что указатель, который он возвращает, после возврата из функции будет указывать на допустимую переменную.

Возврат по адресу часто использовался для возврата вызывающему динамически выделенной памяти:

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

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

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

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

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

Возврат по ссылке

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

Однако, как и при возврате по адресу, вы не должны возвращать по ссылке локальные переменные. Рассмотрим следующий пример:

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

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

Когда мы вызываем getElement(array, 10) , getElement() возвращает ссылку на элемент массива с индексом 10. Затем main() использует эту ссылку для присвоения этому элементу значения 5.

Хотя это в некоторой степени надуманный пример (потому что вы можете получить доступ к array[10] напрямую), как только вы узнаете о классах, вы найдете гораздо больше применений для возврата значений по ссылке.

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

  • При возврате параметра-ссылки.
  • При возврате члена объекта, который был передан в функцию по ссылке или адресу.
  • При возврате большой структуры или класса, который не будет уничтожен в конце функции (например, тот, который был передан по ссылке).

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

  • При возврате переменных, которые были объявлены внутри функции, или параметров, переданных по значению (используйте возврат по значению).
  • При возврате значения встроенного массива или указателя (используйте возврат по адресу).

Смешивание возвращаемых ссылок и значений

Хотя функция может возвращать значение или ссылку, вызывающий может или не может присвоить этот результат переменной или ссылке соответственно. Давайте посмотрим, что происходит, когда мы смешиваем значения и ссылки при возврате.

В случае A мы присваиваем возвращаемое значение-ссылку переменной-нессылке. Поскольку giana не является ссылкой, возвращаемое значение копируется в giana , как если бы returnByReference() выполняла возврат по значению.

В случае B мы пытаемся инициализировать ссылку ref копией значения, возвращаемого функцией returnByValue() . Однако, поскольку возвращаемое значение не имеет адреса (это r-значение), это вызовет ошибку компиляции.

В случае C мы пытаемся инициализировать константную ссылку cref копией значения, возвращаемого returnByValue() . Поскольку константные ссылки могут связываться с r-значениями, здесь нет проблем. Обычно срок действия r-значения истекает в конце выражения, в котором оно создано, однако, когда оно привязывается к константной ссылке, время жизни r-значения (в данном случае возвращаемого значения функции) увеличивается до времени жизни ссылки (в данном случае cref )

Продление времени жизни не сохраняет висячие ссылки

Рассмотрим следующую программу:

В приведенной выше программе returnByReference() возвращает константную ссылку на значение, которое выйдет за пределы области видимости при завершении функции. Обычно это недопустимо, так как это приведет к висячей ссылке. Однако мы также знаем, что присвоение значения константной ссылке может продлить время жизни этого значения. Итак, что здесь имеет приоритет? Что раньше, 5 выходит из области видимости, или ref продлевает срок жизни 5?

Ответ заключается в том, что сначала 5 выходит за пределы области видимости, затем ссылка на 5 копируется обратно вызывающему, а затем ref продлевает время жизни теперь уже висячей ссылки.

Однако следующий код работает должным образом:

В этом случае литеральное значение 5 сначала копируется обратно в область видимости вызывающего ( main ), а затем ref продлевает время жизни этой копии.

Возврат нескольких значений

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

К счастью, есть несколько косвенных методов, которые можно использовать.

Второй метод предполагает использование структуры только для данных:

Третий способ (представленный в C++11) - использовать std::tuple . Кортеж (tuple) – это последовательность элементов, которые могут быть разных типов, где тип каждого элемента должен быть указан явно.

Вот пример, который возвращает кортеж и использует std::get для получения n-го элемента кортежа:

Это работает идентично предыдущему примеру.

Вы также можете использовать std::tie для распаковки кортежа в предопределенные переменные, например:

Начиная с C++17, для упрощения разделения нескольких возвращаемых значений на отдельные переменные может использоваться объявление структурированной привязки:

Использование структуры – лучший вариант, чем кортеж, если вы используете эту структуру в нескольких местах. Однако для случаев, когда вы просто упаковываете эти значения для возврата, и не будет повторного использования при определении новой структуры, кортеж немного чище, поскольку он не вводит новый пользовательский тип данных.

Заключение

В большинстве случаев возврата по значению будет достаточно. Это также самый гибкий и безопасный способ вернуть информацию вызывающему. Однако возврат по ссылке или адресу также может быть полезен, особенно при работе с динамически размещаемыми классами или структурами. При использовании возврата по ссылке или адресу убедитесь, что вы не возвращаете ссылку или адрес переменной, которая при возврате из функции выйдет за пределы области действия!

Небольшой тест

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

1) Функция с именем sumTo() , которая принимает параметр int и возвращает сумму всех чисел между предыдущими значениями и переданным входным числом.

2) Функция с именем printEmployeeName() , которая принимает в качестве входных данных структуру Employee .

3) Функция с именем minmax() , которая принимает на вход два числа int и возвращает вызывающему меньшее и большее числа в std::pair . std::pair работает так же, как std::tuple , но хранит ровно два элемента.

4) Функция с именем getIndexOfLargestValue() , которая принимает массив значений int (как std::vector ) и возвращает индекс самого большого элемента в массиве.

std::max_element – стандартная функция.

5) Функция с именем getElement() , которая принимает массив из std::string (как std::vector ) и индекс и возвращает элемент массива по этому индексу (не копию). Предположим, что индекс действителен, а возвращаемое значение – const .

Рассмотрим ситуации использования операторов перехода Перейти (GoTo), Возврат (Return), Прервать (Break), Продолжить (Continue). Как вы считаете - это дурной тон, нормальная практика или зависит от ситуации?

Этот вопрос появился у нас в процессе обсуждения некоторых технических вопросов и проведения code-review, но мне интересно обсудить его и на данном тематическом ресурсе. Скажу сразу - мнение в нашей команде разделилось.

И действительно Дональд Кнут в свое время писал:

Давайте рассмотрим примеры использования и не использования этих операторов и сравним.

1. Оператор Перейти (GoTo).


2. Оператор Возврат (Return).

Давайте рассмотрим некоторый виртуальный пример. Задача будет следующая: Требуется написать функцию получения математическое ожидание покупок клиента или средний чек.

Какие выводы мы можем сделать из двух вышеуказанных примеров?

3. Оператор Прервать (Break)


Один из самых востребованных операторов, если рассмотреть программирование на C++. В свое время я часто его использовал в связке с Switch и Операторах цикла (For, While). Обратите внимание, что оператор "Прервать" может использоваться и для выхода из бесконечного цикла.
Рассмотрим вариацию типового примера для подключения к клиенту тестирования механизма автоматизированного тестирования от 1С.

Какие отличия мы видим?

  • в первом случае мы последовательно встречаемся с условиями, так проводить анализ значительно проще на мой взгляд
  • во втором случае код внутри процедуры получился значительно меньше и сразу видно условие выхода из цикла
  • Использование "вечных" условий в операторах цикла - это дурной тон для языка 1С Предприятие.

4. Оператор Продолжить (Continue)

Оператор "Продолжить" (continue) позволяет сразу перейти в конец тела цикла, пропуская весь код, который находится под ним. Это полезно в тех случаях, когда мы хотим завершить текущую итерацию раньше времени.

Будьте осторожны при использовании оператора "Продолжить" с циклом "Пока" (while). Поскольку в этих циклах инкремент счетчиков выполняется непосредственно в теле цикла, то использование "Продолжить" может привести к тому, что цикл станет бесконечным!

Рассмотрим задачу - Требуется выполнить предварительную обработку таблицы данных загруженную из Excel.

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

Процедура "НайтиСсылкуПоНаименованию" ищет ссылку контрагента в базе 1С.

Функция "ЭтоБитыеДанные" проверяет наличие "кривой" информации в полях Сумма, КонтрагентНаименование.

Как видно, то в результате необдуманного выбора оператора цикла, установки счетчика и размещения условия прервать, мы уйдем в бесконечный цикл. Лучше переписать так:

Теперь рассмотрим вариант без использования оператора "Продолжить".

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

5. Операторы прервать и продолжить

Многие учебники рекомендуют не использовать операторы "Прервать" (break) и "Продолжить" (continue), поскольку они приводят к произвольному перемещению точки выполнения программы по всему коду, что усложняет понимание и следование логике выполнения такого кода.

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

Функция "ЭтоБитыеДанные" проверяет наличие "кривой" информации в полях Сумма, Контрагент.

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

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

  • Не желание использовать операторы переходов вынудило нас во втором случае полностью переписать код
  • Во втором случае потребовалось заводить лишние переменные
  • Во втором случае потребовалось вынести часть логики из цикла в конец функции, что увеличивает объем кода выполняющий решения поставленной задачи
  • В циклах "Для" и "Для каждого" удобно использовать оператор "Продолжить" для быстрого перехода к следующей итерации по результатам условий, если текущая строка не требует обработки.

Заключение

Я считаю, что использование или не использование операторов зависит от ситуации. В некоторых случаях- это позволяет существенно упростить написание и понимание кода. Однако, в некоторых случаях небрежное или "слепое" использование операторов переходов может привести к нежелательным последствиям.

  • Операторы " Продолжить " и " Прервать " удобно использовать в циклах " Для " и " Для каждого "
  • Если речь идет про оператор цикла " Пока ", то тут лучше стараться не использовать " Продолжить ", т.к. в некоторых комбинациях возможно создание бесконечного цикла.
  • Дурной тон в операторах циклов использовать "вечные" условия по типу " Пока Истина Цикл".
  • При использовании оператора " Возврат " надо следить, чтобы не оставалось недостижимого кода.
  • Также возможны случаи написания недостижимого кода с операторами " Продолжить " и " Прервать ".
  • Оператор " Возврат " удобно использовать в начале функций при выполнении проверок на консистентность (корректность) входных параметров.
  • Оператор " Перейти " запрещено использовать.
  • Если придерживаться структурного подхода, то следует себя ограничивать в создании вложенности (или "матрешек") с условиями " Если Тогда ", так как это затрудняет понимание кода и увеличивает критерий цикломатичности.
  • В процессе разработки (кодирования) всегда выполняем рефакторинг кода и выносим большие блоки в отдельные функции.
  • Используйте в процессе работы анализ качества кода (код-ревью) (По следам код-ревью, Как завести у себя в команде код-ревью. Отвечаем на вопросы)
  • Используйте тестирование и автоматизированное тестирование (Автоматизация тестирования, Пример создания сценарного UI теста для платформы 1С)

Специальные предложения

Electronic Software Distribution

Интеграция 1С с системой Меркурий

Алкогольная декларация

Готовые переносы данных

54-ФЗ

Управление проектом на Инфостарте

Траектория обучения 1С-разработчика

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

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

(1) На счет скролла кода:
1. С психологической точки зрения при постоянном переключении туда-сюда больше устаешь и соответственно теряется эффективность.
2. По некоторым рекомендациям проведения код ревью: чем меньше код и более компактный, тем проще проводить эту процедуру.
3. С точки зрения эффективности анализа кода, гораздо проще понять в чем суть:

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

"Оператор "Перейти" запрещено использовать."
Если бы все было именно так, то этого оператора не было в синтаксисе.

(4) Это правило хорошего тона. Однако, мне встречались реализации на 1С, где довольно часто использовался этот оператор. И выглядел этот кода довольно уныло.
С другой стороны, если взять Ассемблер (или машинные коды), то там как раз в большинстве своем использования операторов перехода и особенно "перейти" (jmp)

(6) при программировании конечных автоматов логичнее использовать goto,
если вы чего-то не . то это не значит что это не . .
Статья из серии "Дейкстру и Вирта не читал , но имею собственное мнение "

Пфффф, у меня по арифметике было "5"!

Выводы гениальны, конечно.. удобно использовать там, где он уместен.
Не понял, откуда у вас получился вывод, что Оператор "Перейти" использовать запрещено?

Прочитал статью по диагонали. НЕ увидел аргументированного вывода по переходу. Он здесь в принципе не раскрыт. Его можно и нужно использовать в условиях когда например тебе нужно выполнить код через Выполнить() и не хочешь чтобы выполнялась громоздкая конструкция в случае когда какая то проверка не выполнилась. По поводу примеров написанных выше скажу только одно. второй пример супер не оптимальный и можно сократить как миниму несколько возвратов, так же разность возвращаемых данных это просто жесть. Когда попробуешь потом возврат обработать еще такую же простыню получишь

В этом уроке мы рассмотрим возврат значений обратно из функции в вызывающий объект всеми этими тремя способами.

Возврат значений (оператор return ) работает почти так же, как и передача значений в функцию. Все те же плюсы и минусы. Основное различие состоит в том, что поток данных двигается уже в противоположную сторону. Однако здесь есть еще один нюанс – локальные переменные, которые выходят из области видимости и уничтожаются, когда функция завершает свое выполнение. Об этом поговорим чуть ниже.

Возврат по значению

Возврат по значению — это самый простой и безопасный тип возврата. При возврате по значению, копия возвращаемого значения передается обратно в caller. Как и в случае с передачей по значению, вы можете возвращать литералы (например, 7), переменные (например, x) или выражения (например, x + 2), что делает этот способ очень гибким.

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

int doubleValue(int a)

int value = a * 3;

return value; // копия value возвращается здесь

> // value выходит из области видимости здесь

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

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

при возврате переменных, которые были объявлены внутри функции;

при возврате аргументов функции, которые были переданы в функцию по значению.

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

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

при возврате больших структур или классов (используйте возврат по ссылке).

Возврат по адресу

Возврат по адресу (он же возврат по указателю ) – это возврат адреса переменной обратно в caller. Подобно передаче по адресу, возврат по адресу может возвращать только адрес переменной. Не литерала и не выражения (они не имеют адресов). Поскольку при возврате по адресу просто копируется адрес из функции в caller, то этот процесс также очень быстрый.

Тем не менее, этот способ имеет один недостаток, который возврат по значению не имеет — если вы попытаетесь вернуть адрес локальной переменной функции, то можете получить неожиданные результаты. Например:

int* doubleValue(int a)

int value = a * 3;

return &value; // value возвращается по адресу здесь

> // value уничтожается здесь

Как вы можете видеть, value уничтожается сразу после того, как его адрес возвращается в caller. Конечным результатом будет то, что caller получит адрес освобожденной памяти ( висячий указатель ) , что, несомненно, вызовет проблемы. Это одна из распространенных ошибок, которую делают новички. Большинство современных компиляторов выдадут предупреждение (а не ошибку), если программист попытается вернуть локальную переменную по адресу, однако есть несколько способов обмануть компилятор, чтобы сделать что-то плохое , не генерируя при этом предупреждения, поэтому вся ответственность лежит на программисте, который должен гарантировать, что возвращаемый адрес будет действителен.

Возврат по адресу часто используется для возврата динамически выделенной памяти обратно в caller:

int* allocateArray(int size)

return new int[size];

int *array = allocateArray(20);

// делаем что-нибудь с array

Здесь не возникнет никакх проблем, так как динамически выделенная память не выходит из области видимости в конце блока, в котором объявлена и все еще будет существовать, когда адрес будет возвращаться в caller.

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

при возврате динамически выделенной памяти;

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

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

при возврате переменных, которые были объявлены внутри функции (используйте возврат по значению);

при возврате большой структуры или класса, который был передан по ссылке (используйте возврат по ссылке).

Возврат по ссылке

Подобно передаче по ссылке, значения, возвращаемые по ссылке, должны быть переменными (вы не сможете вернуть ссылку на литерал или выражение). При возврате по ссылке в caller возвращается ссылка на переменную. Затем caller может её использовать для продолжения изменения переменной, что может быть иногда полезно. Этот способ также очень быстрый и при возврате больших структур или классов.

Однако, как и в возврате по адресу, вы не должны возвращать локальные переменные по ссылке. Рассмотрим следующий фрагмент кода:

int& doubleValue(int a)

int value = a * 3;

return value; // value возвращается по ссылке здесь

> // value уничтожается здесь

В программе выше возвращается ссылка на переменную value, которая уничтожится, когда функция завершит свое выполнение. Это означает, что caller получит ссылку на мусор. К счастью, ваш компилятор, вероятнее всего, выдаст предупреждение или ошибку, если вы попытаетесь это сделать.

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

// Возвращаем ссылку на индекс элемента массива

int& getElement(std::array &array, int index)

// мы знаем, что array[index] не уничтожится, когда мы будем возвращать данные в caller (так как caller сам передал этот array в функцию!)

// так что здесь не должно быть никаких проблем с возвратом по ссылке

// присваиваем элементу массива с индексом 15 значение 7

getElement(array, 15) = 7;

Когда мы вызываем getElement(array, 15) , то getElement() возвращает ссылку на элемент массива с индексом 15, затем main() использует эту ссылку для присвоения этому элементу значения 7.

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

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

при возврате ссылки-параметра;

при возврате элемента массива, который был передан в функцию;

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

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

при возврате переменных, которые были объявлены внутри функции (используйте возврат по значению);

при возврате стандартного массива или значения указателя (используйте возврат по адресу).

Смешивание возвращаемых значений и ссылок

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

// static гарантирует то, что переменная y не уничтожится,

// когда выйдет из локальной области видимости

static int y = 7;

// Случай A - всё хорошо, обрабатывается как возврат по значению

int value = returnByReference();

// Случай B - ошибка компилятора, так как 7 - это r-value,

// а r-value не может быть привязано к неконстантной ссылке

// Случай C - всё хорошо, время жизни возвращаемого значения

// продлевается в соответствии со временем жизни cref

В случае A мы присваиваем ссылку возвращаемого значения переменной, которая сама не является ссылкой. Поскольку value не является ссылкой, возвращаемое значение просто копируется в value, как если бы returnByReference() был возвратом по значению.

В случае B мы пытаемся инициализировать ссылку ref копией возвращаемого значения функции returnByValue(). Однако, поскольку возвращаемое значение не имеет адреса (это r-value ), то мы получим ошибку компиляции.

В случае C мы пытаемся инициализировать константную ссылку cref копией возвращаемого значения функции returnByValue(). Поскольку константные ссылки могут быть инициализированы r-values, то здесь не должно быть никаких проблем. Обычно r-values уничтожаются в конце выражения, в котором они созданы, однако при привязке к константной ссылке время жизни r-value (в данном случае, возвращаемого значения функции) продлевается в соответствии со временем жизни ссылки (в данном случае, cref).

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

Читайте также: