Что такое унарный минус
Перейти к содержимому

Что такое унарный минус

  • автор:

Унарные операторы «плюс» и «отрицание»: + и —

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

Над целочисленными операндами выполняется восходящее приведение целого типа. Результирующим типом является тип, до которого повышается уровень операнда. Таким образом, выражение +ch , где ch имеет тип char , приводит к типу int ; значение не изменено. Дополнительные сведения о том, как это делается, см. в разделе «Стандартные преобразования».

— — оператор

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

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

Блок, относящийся только к системам Майкрософт

Унарное отрицание величин без знака выполняется путем вычитания значения операнда из числа 2^n, где n — количество битов в объекте заданного типа без знака.

Завершение блока, относящегося только к системам Майкрософт

Что такое унарный минус

Argument ‘Topic id’ is null or empty

Сейчас на форуме

© Николай Павлов, Planetaexcel, 2006-2023
info@planetaexcel.ru

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

ООО «Планета Эксел»
ИНН 7735603520
ОГРН 1147746834949
ИП Павлов Николай Владимирович
ИНН 633015842586
ОГРНИП 310633031600071

C++. Унарный минус и беззнаковый тип

Привет, Хабр! Меня зовут Владимир, я работаю в VK Карты. Хочу рассказать про случай, который недавно произошёл у нас в подразделении. Он кажется достаточно типичным и может быть интересен другим программистам.

Нам, программистам на C++, не привыкать, что даже самый безобидный код может таить в себе сюрпризы. Рассмотрим пример:

uint32_t width = 7; int32_t signed_offset = -width;

Он полон сюрпризов! Каких? Короткий ответ: значение signed_offset не определено стандартом и зависит от реализации. Но это далеко не все неожиданности в этом коде. Статья как раз о них.

Результат, возвращаемый унарным минусом

Давайте сначала разберемся с тем, что возвращает -width . Это может прозвучать неожиданно, но тип, возвращаемый -width , это uint32_t . И это не опечатка. То есть если унарный минус применить к неотрицательному числу, то в результате получим опять же неотрицательное число. Давайте разберёмся, как такое могло получиться и чему будет равен результат -width .

Если выполнить такой код:

uint32_t width = 7; auto offset = -width; std::cout uint32_t width = 7; int32_t signed_offset = -width;

Как мы выяснили выше, -width вернёт значение 4 294 967 289 типа uint32_t . А оно не поместится в int32_t . Соответственно, значение signed_offset будет не определено. Отмечу, что согласно стандарту это допустимая запись, не приводящая к неопределенному поведению. То есть код допустим, но значение в signed_offset после его исполнения может быть любым.

Если скомпилировать и запустить этот пример, то с большой долей вероятности в signed_offset окажется -7. Так реагирует Clang 13.1.6 на MacOS. Но это реакция на переполнение знакового целого числа. А с точки зрения стандарта в signed_offset может быть любое значение.

Переполнение знаковых и беззнаковых типов

Отмечу, что стандарт по-разному относится к переполнению знаковых и беззнаковых типов. При присвоении слишком большого значения знаковому типу мы получаем поведение, зависящее от реализации. Этот случай мы подробно рассмотрели выше. При присвоении слишком большого значения беззнаковому типу мы получим вполне определённое и гарантированное стандартом поведение, согласно разделу 7.8.2. Правило такое: если мы хотим присвоить число big_number переменной unsigned_offset беззнакового типа some_unsigned_type, то результатом этой операции будет число big_number по модулю 2 n , где n — количество бит, необходимое для хранения типа some_unsigned_type . Звучит запутано, но на самом деле тут всё предельно просто. Достаточно взглянуть на пример:

uint64_t big_unsigned_offset = 4294967296LLU + 4294967296LLU + 7LLU; // 2^32 = 4294967296 uint32_t unsigned_offset = big_unsigned_offset;

unsigned_offset будет равно 7, потому что размер uint32_t — 32 бита. 2 32 равно 4 294 967 296, а 4 294 967 296 + 4 294 967 296 по модулю 4 294 967 296 будет равно 0. Выходит результат 7.

Выводы и рекомендации

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

uint32_t width = 7; int32_t signed_width = width; int32_t signed_offset = -signed_width;

Во всех рассуждениях я преимущественно писал про uint32_t и int32_t . Однако эти рассуждения верны соответственно для всех знаковых и беззнаковых типов.

Ссылки

Хочу кое-что уточнить. То что я писал выше относится к стандарту C++17. В C++20 приняли «two’s complement». Это раздел 6.8.1, параграф 3. То есть код из начала статьи:

uint32_t width = 7; int32_t signed_offset = -width;

должен всегда работать одинаково в C++20, и signed_offset должно быть -7. В C++17 значение signed_offset не определено.

Перегрузка унарных операторов

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

Унарные операторы, доступные для перегрузки

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

Объявления перегрузки унарных операторов

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

Следующие правила применяются ко всем унарным операторам префикса. Чтобы объявить унарную функцию оператора как нестатическую функцию-член, используйте эту форму объявления:

return-type operator op ();

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

Чтобы объявить унарную функцию оператора как немемберную функцию, используйте эту форму объявления:

return-type operator op ( class-type );

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

Формы постфикса ++ и принимают дополнительный int аргумент, чтобы отличить их от форм префикса. Дополнительные сведения о префиксе и постфиксных формах ++ и см. в разделе «Добавочное и уменьшение» перегрузки оператора.

Нет ограничений на возвращаемые типы унарных операторов. Например, для логического НЕ ( ! ) имеет смысл возвращать bool значение, но это поведение не применяется.

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

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