Какова цель модификаторов 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 (аргумент будет повышен в соответствии с продвижением целых чисел, но его значение должно быть преобразовано в тип short int). или unsigned short int перед печатью); или что следующий спецификатор преобразования n применяется к указателю на короткий аргумент типа 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);

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

Один реальный случай, который я видел, это такой код:

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

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

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


person R.. GitHub STOP HELPING ICE    schedule 03.01.2011    source источник


Ответы (7)


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

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

В C89 было добавлено преобразование входных указателей с помощью %p, хотя оно явно рискованное, для симметрии с fprintf.

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

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

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

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

person Michael Burr    schedule 03.01.2011
comment
Согласны ли вы с моей предварительной оценкой того, что соответствующая реализация может игнорировать модификаторы h и hh? - person R.. GitHub STOP HELPING ICE; 03.01.2011
comment
Я не уверен - я не уверен, что это приведет к неопределенному поведению: printf("%hu", (unsigned int) 0x10000);. Я могу представить аргументы в обоих направлениях - я бы предпочел, чтобы они были четко определены, но мог видеть, что формулировка указывает, что следующий спецификатор преобразования d, i, o, u, x или X применяется к короткому int или unsigned short Аргумент int отбрасывает это на неопределенную территорию, хотя непосредственно следующий (аргумент будет повышен в соответствии с целочисленными продвижениями, но его значение должно быть преобразовано в short int или unsigned short int перед печатью) отбрасывает его обратно. - person Michael Burr; 03.01.2011
comment
основываясь на этом тексте, я думаю, что для реализации было бы разумно преобразовать в short int или unsigned short int, используя оптимизированный код, который предполагает, что преобразуемое значение действительно является результатом продвижения, как говорит стандарт. Упомянутый оптимизированный код мог бы предположительно сделать что-то бессмысленное со значением, выходящим за пределы допустимого диапазона, так что есть, по крайней мере, правдоподобное заявление реализации о том, что это должно быть неопределенным поведением, и что код имеет нарушил требование стандарта. - person Steve Jessop; 12.01.2011
comment
@R..: Я не вижу ничего, что запрещало бы реализации игнорировать их. Однако даже если бы они ничего не делали, включение их в спецификацию означало бы, что программа, выполняющая printf("%hx",1u);, имела бы определенное поведение; Напротив, без текста, указывающего, что h является допустимым модификатором, такая программа будет UB, не так ли? - person supercat; 24.04.2015

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

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

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

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

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

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

person Adam Norberg    schedule 03.01.2011
comment
Откуда вы взяли, что отрицательные числа печатаются в их битовых формах? Насколько я могу судить, передача отрицательного значения для любого беззнакового спецификатора формата (%x, %u или %o) приводит к неопределенному поведению. Кроме того, насколько я могу судить, соответствующая реализация может просто игнорировать наличие любого модификатора h или hh, кроме %n. - person R.. GitHub STOP HELPING ICE; 03.01.2011
comment
Приведения между (unsigned) и (signed) в пределах одной ширины гарантированно не вносят фактических изменений в битовый шаблон данных, а просто интерпретируют этот битовый шаблон. (Приведения, которые изменяют ширину, дополняются нулями или дополняются знаком, в зависимости от ситуации.) %x определено для работы со значениями без знака, поэтому они сначала преобразуются из знакового в беззнаковое, что не изменяет данные, но изменяет интерпретацию в действии, использование %x с отрицательным числом показывает вам его битовый шаблон. А %x — целочисленный тип, а модификатор h работает с целочисленными типами, поэтому я думаю, что он поддерживается. - person Adam Norberg; 03.01.2011
comment
Ваша информация заведомо неверна. C определяет преобразования (неявные или приведенные) с точки зрения значений, а не битовых шаблонов. Преобразования в беззнаковые типы определяются стандартом способом, эквивалентным модульной арифметике. Преобразования в типы со знаком определяются реализацией, за исключением случаев, когда значение соответствует целевому типу без изменений. - person R.. GitHub STOP HELPING ICE; 03.01.2011
comment
Что касается h на %x, цитата из linux.die.net/man/3/printf , в отношении модификаторов длины: здесь «целочисленное преобразование» означает преобразование d, i, o, u, x или X. Таким образом, %x и %X являются, по крайней мере, в Linux, официально включенными в область того, к чему формально может быть присоединен модификатор h. - person Adam Norberg; 03.01.2011
comment
Конечно, %hx действителен. Это указано в стандарте. Но для %hx требуется аргумент unsigned short, который повышается до положительного int, который (согласно требованиям стандарта) имеет то же представление, что и соответствующее значение unsigned int. Таким образом, насколько я могу судить, %x должен работать так же хорошо. - person R.. GitHub STOP HELPING ICE; 03.01.2011
comment
На самом деле, C предназначен для выполнения преобразований из отрицательного в положительное из знакового в беззнаковое путем добавления UINT_MAX. Вы совершенно правы, что это абсолютно ничего не делает с битовым шаблоном числа в компьютере с дополнением до 2. (Приведение к меньшему беззнаковому типу зависит от реализации, но не к типу того же размера или большего размера.) Таким образом, мой совет касается и только машин, которые используют дополнение до 2 для своей целочисленной арифметики. Измените свой код, если вы нацелены на тот, который этого не делает. - person Adam Norberg; 03.01.2011
comment
Мой вопрос касается языка C, а не какой-либо реализации. И добавление UINT_MAX неверно. Вы забыли +1, среди других деталей. Как только вы это исправите, это станет эквивалентно модульной арифметике. - person R.. GitHub STOP HELPING ICE; 03.01.2011
comment
Важно, когда произойдет преобразование. %hx не имеет значения для компилятора — все, о чем заботится компилятор, — это повышающее преобразование от short до signed int в вариативном параметре. Таким образом, он может сделать расширение знака, которое вам не нужно. Конечно, это применимо только в том случае, если вы передали подписанный шорт, а затем попытались использовать его, как если бы он был неподписанным. Учитывая, сколько оскорблений printf было совершено за эти годы, это не неправдоподобный случай. %hx ничего не должен делать при использовании строго по закону, но можно с уверенностью сказать, что строгая законность маловероятна. - person Adam Norberg; 03.01.2011
comment
Вы правы, я сбросил +1; Исправлю, когда накатлю обновление до ответа. Как бы то ни было, я думаю, мы довольно четко определили, что практическое использование %hx ограничено случаями, когда printf используется нелегально (для представления аргумента со знаком как беззнакового, что обычно считается безопасным, но безопасен только на машине с дополнением 2; результатом является довольно много неработающего кода в общих библиотеках на машинах, отличных от 2'c), что делает его по своей сути специфичным для реализации. С рациональной точки зрения в этом нет особого смысла, когда преобразование уже произошло в очень узком допустимом диапазоне. - person Adam Norberg; 03.01.2011

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

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

person caf    schedule 04.01.2011
comment
Если unsigned short или unsigned char повышается до int, это все еще положительно, поэтому C требует, чтобы представление совпадало с представлением для unsigned. Насколько я знаю, несоответствие знака допустимо в аргументах функций с переменным числом аргументов и аргументах функций без прототипов, если значение положительно, как значение со знаком. Конечно, %x предназначен для работы с int аргументами, пока они положительные... - person R.. GitHub STOP HELPING ICE; 04.01.2011
comment
@R.: Для общих функций с переменным числом вы правы, но для конкретного случая семейства printf стандарт дает unsigned int в качестве типа аргумента для %x, а позже говорит Если какой-либо аргумент не является правильным тип для соответствующей спецификации преобразования, поведение не определено. - что, я не верю, позволяет вам передать int. - person caf; 04.01.2011
comment
Интересный. Хотя я подозреваю, что это непреднамеренно. Возможно, мне следует просмотреть стандарт и посмотреть, есть ли примеры, подобные printf("%x", 1); (по вашим рассуждениям это должно быть 1U вместо 1). - person R.. GitHub STOP HELPING ICE; 05.01.2011
comment
Это не UB, если значение находится в диапазоне обоих значений, int и signed int, потому что эти значения можно использовать как взаимозаменяемые. Они специально упоминают вызовы функций. См. сноску 31 в стандарте C99 или сноску 41 в C11 в разделе 6.2.5 «Типы». - person 12431234123412341234123; 17.09.2020
comment
@ 12431234123412341234123: Это то, что обсуждалось в предыдущих комментариях. Это верно для вызовов функций с переменным числом аргументов в целом, но для конкретного случая функций printf существует специальный переопределяющий язык (в C11 7.21.6.1 p9). Конечно, это довольно педантичный момент, и, как говорит Р. выше, он может быть непреднамеренным. - person caf; 18.09.2020

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

person Jonathan Leffler    schedule 03.01.2011
comment
Суть моего вопроса заключалась в том, что если вы не передаете неправильные типы способами, которые в любом случае приводят к неопределенному поведению, маскирование/преобразование будет неэффективным (с точки зрения ценности). - person R.. GitHub STOP HELPING ICE; 05.01.2011

Я нашел полезным избегать приведения при форматировании беззнаковых символов в шестнадцатеричный:

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

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

person mzimmers    schedule 27.12.2017
comment
какой тип CEkey в этом ответе? Поведение не определено, если оно не было unsigned char * ; или, если это так, hh является избыточным. - person M.M; 15.06.2020

еще одно удобное место — проверка размера 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

person rafi wiener    schedule 01.01.2018
comment
Интересный. Это похоже на обходной путь для мысли об ошибке gcc. Для уровня 1 предупреждения -Wformat-overflow gcc документирует, что считает числовые аргументы, которые, как известно, ограничены поддиапазоном их типа, что всегда имеет место для продвигаемых символов. Но уровень 2 не описывает это поведение...? gcc.gnu.org/onlinedocs/gcc/Warning-Options.html - person R.. GitHub STOP HELPING ICE; 01.01.2018
comment
я работаю с libvma, и мы отправили эту фиксацию для компиляции с помощью gcc7. я не уверен, какой уровень переполнения мы использовали (я думаю, по умолчанию) - person rafi wiener; 02.01.2018

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

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

person Jens Gustedt    schedule 03.01.2011