Какова цель модификаторов h и hh для printf?

Помимо %hn и %hhn (где h или hh указывает размер объекта, на который указывает объект), какова точка модификаторов h и hh для printf формата printf ?

Из-за рекламных акций по умолчанию, которые требуются стандарту для применения к вариационным функциям, невозможно передать аргументы типа char или short (или любые подписанные / неподписанные варианты) для printf .

Согласно 7.19.6.1 (7), модификатор h :

Указывает, что для краткого int или unsigned short int аргумент применяется следующий d, i, o, u, x или X вариант преобразования (аргумент будет продвигаться в соответствии с целыми рекламными акциями, но его значение должно быть преобразовано в short int или unsigned short int перед печатью); или что для указателя на короткий аргумент int применяется следующий параметр преобразования n.

Если аргумент был на самом деле short или unsigned short шрифтом типа short , то продвижение к int за которым следует преобразование обратно к short или unsigned short приведет к тому же значению, что и продвижение к int без возврата. Таким образом, для аргументов типа short или unsigned short , %d , %u и т. Д. Должны давать одинаковые результаты %hd , %hu и т. Д. (А также для типов char и hh ).

Насколько я могу судить, единственной ситуацией, когда модификатор h или hh может быть полезен, является то, что аргумент передал ему int вне диапазона short или unsigned short , например

 printf("%hu", 0x10000); 

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

Один реальный случай, который я видел, выглядит следующим образом:

 char c = 0xf0; printf("%hhx", c); 

где автор ожидает, что он будет печатать f0 несмотря на то, что реализация имеет тип открытого char который подписан (в этом случае printf("%x", c) будет печатать fffffff0 или аналогичный). Но оправданно ли это ожидание?

(Примечание. Что происходит, это то, что исходным типом был char , который получает повышение до int и преобразовывается обратно в unsigned char вместо char , тем самым изменяя значение, которое печатается. Но указывает ли стандарт на это поведение или это реализация подробно о том, что сломанное программное обеспечение может полагаться?)

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

Хотя они не упоминают важность симметрии для модификаторов «h» и «hh» в документе Rational Rationale , комитет упоминает это как соображение о том, почему спецификатор преобразования «% p» поддерживается для fscanf() ( хотя это не ново для C99 – поддержка «% p» находится в C90):

Преобразование указателя ввода с% p было добавлено в C89, хотя оно явно рискованно для симметрии с fprintf.

В разделе, посвященном fprintf() , в документе обоснования C99 обсуждается, что «hh» было добавлено, а просто обращается к читателю в fscanf() :

Модификаторы% hh и% ll были добавлены в C99 (см. §7.19.6.2).

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

Кроме того, для полноты модификатор «h» был в оригинальном стандарте C89 – предположительно, он был бы там, даже если он не был строго необходим из-за широко распространенного использования, даже если, возможно, не было технического требования использовать модификатор ,

В режиме %...x все значения интерпретируются как unsigned. Поэтому отрицательные числа печатаются как их беззнаковые преобразования. В арифметике дополнений 2, используемой большинством процессоров, нет разницы в битовых шаблонах между подписанным отрицательным числом и его положительным беззнаковым эквивалентом, который определяется арифметикой модуля (добавляя максимальное значение для поля плюс одно к отрицательному числу, согласно к стандарту C99). Многие программные средства, особенно код отладки, который, скорее всего, будет использовать %x делают молчаливое предположение о том, что представление битов знакового отрицательного значения и его неподписанное значение одинаково, что справедливо только для машины дополнений 2.

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

Отрицательный short отображаемый как unsigned long в hexidecimal, будет поэтому на любой машине дополняться f , из-за неявного расширения знака в продвижении, которое printf будет печатать. Значение одно и то же, но оно действительно визуально вводит в заблуждение относительно размера поля, подразумевая значительное количество диапазона, которого просто нет.

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

Поведение printf не определено при передаче int за пределами short диапазона, который должен быть напечатан как short , но самая простая реализация гораздо проще отбрасывает высокий бит необработанным downcast, поэтому, в то время как спецификация не требует какого-либо конкретного поведения , практически любая нормальная реализация будет просто выполнять усечение. Однако, как правило, есть лучший способ сделать это.

Если printf не является добавлением значений или отображает неподписанные представления подписанных значений, %h не очень полезен.

Единственное, что я могу придумать, это передать unsigned short или unsigned char и использовать спецификатор преобразования %x . Вы не можете просто использовать голый %x – значение может быть увеличено до int а не unsigned int , а затем у вас есть неопределенное поведение.

Ваши альтернативы – либо явно передать аргумент unsigned ; или использовать %hx / %hhx с открытым аргументом.

Парадигматические аргументы 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-битное количество; стандарт на самом деле не гарантирует это, конечно.

Мне было удобно избегать кастинга при форматировании беззнаковых символов в hex:

  sprintf_s(tmpBuf, 3, "%2.2hhx", *(CEKey + i)); 

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

Я согласен с вами в том, что это не является строго необходимым, и поэтому по этой причине в функции библиотеки C нет ничего полезного 🙂

Это может быть «приятно» для симметрии разных флагов, но в основном это контрпродуктивно, потому что оно скрывает правило «преобразование в int ».

В другом месте это удобно – проверка размера snprintf. gcc7 добавил проверку размера при использовании snprintf, чтобы это не получилось

 char arr[4]; char x='r'; snprintf(arr,sizeof(arr),"%d",r); 

поэтому он заставляет вас использовать более крупный символ при использовании% d при форматировании символа

вот фиксация, которая показывает эти исправления, а не увеличивая размер массива символов, они изменили% d на% h. это также дает более точное описание

https://github.com/Mellanox/libvma/commit/b5cb1e34a04b40427d195b14763e462a0a705d23#diff-6258d0a11a435aa372068037fe161d24

Давайте будем гением компьютера.