Какова цель модификаторов h и hh для printf?
Помимо %hn
и %hhn
(где h
или hh
указывает размер объекта, на который указывает объект), какова точка модификаторов h
и hh
для printf
спецификаторы формата?
Из-за рекламных акций по умолчанию, которые требуются стандарту для применения к вариационным функциям, невозможно передать аргументы типа char
или short
(или любые его подписанные/неподписанные варианты) на printf
.
В соответствии с 7.19.6.1 (7) модификатор h
:
Указывает, что для следующего преобразования d, i, o, u, x или X применяется к short int или unsigned short int (аргумент будет были повышены в соответствии с целыми рекламными акциями, но его стоимость должна преобразовываться в короткий int или unsigned short int перед печатью); или что для указателя на короткий int.
Если аргумент был фактически типа short
или unsigned short
, то продвижение до int
с последующим преобразованием обратно в short
или unsigned short
даст то же значение, что и продвижение до int
, без каких-либо изменение задний. Таким образом, для аргументов типа short
или unsigned short
, %d
, %u
и т.д. Должны давать одинаковые результаты %hd
, %hu
и т.д. (А также для типов char
и hh
).
Насколько я могу судить, единственная ситуация, когда модификатор h
или hh
может быть полезен, - это когда аргумент передал ему int
вне диапазона short
или unsigned short
, например
printf("%hu", 0x10000);
но я понимаю, что передача неправильного типа, как и в результате, приводит к поведению undefined в любом случае, так что вы не могли ожидать, что он будет печатать 0.
Один реальный случай, который я видел, выглядит следующим образом:
char c = 0xf0;
printf("%hhx", c);
когда автор ожидает, что он напечатает f0
, несмотря на то, что реализация имеет простой тип char
, который был подписан (в этом случае printf("%x", c)
будет печатать fffffff0
или аналогичный). Но оправдано ли это ожидание?
(Примечание. Что происходит, так это то, что исходный тип был char
, который получил повышение до int
и преобразован обратно в unsigned char
вместо char
, тем самым изменяя значение, которое печатается. стандарт указывает это поведение, или это деталь реализации, на которую может сломаться сломанное программное обеспечение?)
Ответы
Ответ 1
Одна возможная причина: для симметрии с использованием этих модификаторов в форматированных входных функциях? Я знаю, что это не было бы строго необходимо, но, может быть, для этого была важна оценка?
Хотя они не упоминают важность симметрии для модификаторов "h" и "hh" в документе C99 Rationale, комитет упоминает это как соображение о том, почему спецификатор конверсии "% p" поддерживается для fscanf()
(хотя это не было новым для C99 - поддержка "% p" находится на C90):
Преобразование указателя ввода с% p было добавлено в C89, хотя оно явно рискованно для симметрии с fprintf.
В разделе fprintf()
в документе обоснования C99 обсуждается, что "hh" был добавлен, а просто обращается к читателю в раздел fscanf()
:
Модификаторы длины% hh и% ll были добавлены в C99 (см. §7.19.6.2).
Я знаю, что это тонкая нить, но я все-таки размышляю, поэтому я решил, что дам любой аргумент, который может быть.
Кроме того, для полноты модификатор "h" был в исходном стандарте C89 - по-видимому, он был бы там, даже если он не был строго необходим из-за широко распространенного использования, даже если бы не было технических требований к используйте модификатор.
Ответ 2
В режиме %...x
все значения интерпретируются как unsigned. Поэтому отрицательные числа печатаются как их беззнаковые преобразования. В арифметике с двумя дополнениями, используемой большинством процессоров, нет разницы в битовых шаблонах между подписанным отрицательным числом и его положительным беззнаковым эквивалентом, который определяется арифметикой модуля (добавляя максимальное значение для поля плюс одно к отрицательному числу, согласно к стандарту C99). Многие программные средства, особенно код отладки, наиболее вероятно используемый для использования %x
, делают молчаливое предположение о том, что представление битов знакового значения с отрицательным знаком и его беззнаковое преобразование одинаково, что справедливо только для машины с двумя дополнениями.
Механика этого литья такова, что шестнадцатеричные представления значения всегда подразумевают, возможно, неточно, что число было отображено в 2 дополнениях, если оно не попало в краевое условие того, где разные целочисленные представления имеют разные диапазоны. Это даже справедливо для арифметических представлений, где значение 0 не представлено двоичным паттерном всех 0s.
Отрицательный short
, отображаемый как unsigned long
в hexidecimal, поэтому на любой машине будет дополняться f
из-за неявного расширения знака в продвижении, которое printf
будет печатать. Значение одно и то же, но оно действительно визуально вводит в заблуждение относительно размера поля, подразумевая значительное количество диапазона, которого просто нет.
%hx
обрезает отображаемое представление, чтобы избежать этого дополнения, точно так же, как вы сделали вывод из своего практического примера использования.
Поведение printf
равно undefined при передаче int
вне диапазона short
, которое должно быть напечатано как short
, но самая простая реализация гораздо проще отбрасывает высокий бит необработанным downcast, поэтому, пока спецификация не требует какого-либо конкретного поведения, практически любая разумная реализация просто выполняет усечение. Однако, как правило, это лучший способ сделать это.
Если printf не является добавлением значений или отображает неподписанные представления подписанных значений, %h
не очень полезен.
Ответ 3
Единственное, о чем я могу думать, это передать unsigned short
или unsigned char
и использовать спецификатор преобразования %x
. Вы не можете просто использовать голый %x
- значение может быть увеличено до int
, а не unsigned int
, а затем у вас есть поведение undefined.
Ваши альтернативы - либо явно передать аргумент unsigned
; или использовать %hx
/%hhx
с открытым аргументом.
Ответ 4
Мне было удобно избегать кастинга при форматировании беззнаковых символов в hex:
sprintf_s(tmpBuf, 3, "%2.2hhx", *(CEKey + i));
Это незначительное удобство кодирования и выглядит более чистым, чем несколько отливок (IMO).
Ответ 5
Парадигматические аргументы printf()
и др. автоматически рекламируются с использованием преобразований по умолчанию, поэтому любые значения short
или char
повышаются до int
при передаче функции.
В отсутствие модификаторов h
или hh
вам нужно будет замаскировать переданные значения, чтобы надежно получить правильное поведение. С помощью модификаторов вам больше не нужно маскировать значения; реализация printf()
выполняет работу должным образом.
В частности, для формата %hx
код внутри printf()
может сделать что-то вроде:
va_list args;
va_start(args, format);
...
int i = va_arg(args, int);
unsigned short s = (unsigned short)i;
...print s correctly, as 4 hex digits maximum
...even on a machine with 64-bit `int`!
Я беспечно полагаю, что short
- это 16-разрядное количество; стандарт на самом деле не гарантирует это, конечно.
Ответ 6
Я согласен с вами в том, что это не является строго необходимым, и поэтому по этой причине в библиотеке C не работает:)
Это может быть "хорошо" для симметрии разных флагов, но в основном это контрпродуктивно, поскольку оно скрывает правило "преобразование в int
".