Какова цель модификаторов 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".