Превосходные методы C/С++ с использованием подписанных/неподписанных функций и вызовов функций
Я задаю этот вопрос для двух разных языков: C и С++.
Какова наилучшая практика при вызове функций, у которых есть противоположное целочисленное знаковое знакомство с тем, что нам нужно в нашем коде?
Например:
uint32 _depth; // uint32 = DWORD
int depth;
_BitScanForward(&_depth, (uint32)input); // DWORD, DWORD
depth = (int)_depth;
_BitScanForward ожидает параметры DWORD (uint32). Переменная input
имеет тип int16, и мне нужно обработать результат _depth
как int32 в моем коде.
- Нужно ли мне заботиться о литье
input
, как показано? Я знаю, что complier, вероятно, сделает это для меня, но что такое лучшая практика?
- Допустимо ли объявлять
_depth
как int32 и, следовательно, не нужно делать его потом, как показано?
Примечание:
Мой комментарий о complier основан на опыте. Я написал код, который скомпилирован без предупреждений в VS, но разбился на выполнение. Оказалось, что я вызываю функцию с шириной int int. Поэтому я больше не оставляю эту тему в компиляторе.
EDIT:
Ответы полезны, спасибо. Позвольте мне уточнить мой вопрос. Если проблем с шириной нет, т.е. Функция не ожидает более узкого int, чем то, что передается (obvioulsy будет терпеть неудачу), тогда можно ли полагаться на компилятор для обработки различий знака и ширины?
Ответы
Ответ 1
Очень важно написать явный прилив при переходе от любого целочисленного типа, который уже, чем int
, к любому целочисленному типу, который имеет одинаковую ширину или ширину, чем int
. Если вы этого не сделаете, компилятор сначала преобразует значение в int
из-за правил "целочисленного продвижения", а затем к типу назначения. Это почти всегда неправильно, и мы не будем разрабатывать язык таким образом, если бы мы начали с нуля сегодня, но мы застряли с ним ради обеспечения совместимости.
Системные typedefs, такие как uint16_t
, uint32_t
, WORD
и DWORD
могут быть более узкими, более широкими или равными размерам int
; в С++ вы можете использовать шаблоны, чтобы понять это, но в C вы не можете. Поэтому вы можете захотеть написать явные приведения для любого преобразования, включающего эти.
Ответ 2
Я бы настоятельно рекомендовал скрыть эту функцию в пользовательскую функцию-обертку, которая согласуется с вашим предпочтительным API (и внутри этой функции делать правильное явное литье). В случае использования специфичных для компилятора функций это имеет дополнительное преимущество в том, что будет намного проще переносить его на разные компиляторы (если вы когда-нибудь захотите это сделать), просто переустановив эту функцию-обертку.
Ответ 3
Ну, это зависит от вашего использования и т.д.:
Если я могу использовать необходимый тип, я просто использую этот тип.
Если нет:
Ваш компилятор должен предупредить вас в случаях, когда вы неявно конвертируете типы данных, которые могут привести к превышению/недопущению. Поэтому я обычно предупреждаю об этом и меняю неявное преобразование на явные.
Там у меня есть два разных подхода:
Если я на 100% уверен, что я никогда не переполняю границы между подписанным /unsigned int, я использую static_cast
. (обычно для преобразования различных API-интерфейсов. Например, size() возвращает int vs size_t).
Когда я не уверен или, возможно, я могу выйти за рамки, которые я использую boost::numeric_cast
. Это вызывает исключение, когда вы выходите за пределы границ и, таким образом, показывает, когда это происходит.
Подход с исключениями придерживается практики, чтобы терпеть неудачу/сбой/завершение, если что-то пошло не так, вместо продолжения с поврежденными данными, а затем сбой в другом месте или другие вещи с данными undefined.
Ответ 4
Сначала ваш компилятор сделает трансляции неявными и предоставит вам предупреждение на любом значащем уровне предупреждения.
Оба действия, которые вы выполняете, - это броски, в которых компилятор (или ваши коллеги) не может легко решить, правильны ли они, поэтому явное преобразование или явное преобразование с помощью пограничного теста - лучшая практика. Вы выбираете, зависит от ваших данных. Самый безопасный способ - проверить граничные условия. Самый дешевый способ - просто бросить (в С++ используйте static_cast, а не C-стиль).