Почему существуют модификаторы длины hh и h в printf?

В функциях с переменным числом аргументов происходит повышение аргументов по умолчанию.

6.5.2.2.6 Если выражение, обозначающее вызываемую функцию, имеет тип, который не включает прототип, для каждого аргумента выполняется целочисленное повышение, а аргументы, имеющие тип float, повышаются до double. Они называются повышениями аргументов по умолчанию. [...]

6.5.2.2.7 [...] Обозначение с многоточием в деклараторе прототипа функции приводит к остановке преобразования типа аргумента после последнего объявленного параметра. Продвижение аргументов по умолчанию выполняется для завершающих аргументов.

Следовательно,

signed char c = 123;
int         i = 123;
float       f = 123;
double      d = 123;

printf("%d\n", i);   // ok
printf("%d\n", c);   // ok, even though %d expects int.
printf("%f\n", d);   // ok
printf("%f\n", f);   // ok, even though %f expects double.

Так почему же существует модификатор длины printf для char (hh) и short (h)?


Номер раздела относится к N2176.


person ikegami    schedule 01.07.2021    source источник
comment
Попробуйте signed char или short установить самый старший бит....   -  person Andrew Henle    schedule 02.07.2021
comment
Эти модификаторы сообщают printf() отменить продвижение по умолчанию, чтобы получить исходное значение char или short.   -  person Barmar    schedule 02.07.2021
comment
@Andrew Henle, Re Попробуйте signed char или short установить самый старший бит..., они прекрасно работают с %d. (signed char)-1 и (short)-1 повышаются до (int)-1, с чем %d справляется отлично. Вы сделали ошибку?   -  person ikegami    schedule 02.07.2021
comment
@Barmar, Re Эти модификаторы говорят printf() отменить продвижение по умолчанию, чтобы получить исходное символьное или короткое значение., Что нужно отменить? Целочисленные акции не влияют на стоимость   -  person ikegami    schedule 02.07.2021
comment
Симметрия с scanf(). То же самое для l в "%lf".   -  person chux - Reinstate Monica    schedule 02.07.2021
comment
И то же самое для %i и %d. Они делают то же самое в printf(), но отличаются в scanf().   -  person Barmar    schedule 02.07.2021
comment
godbolt.org/z/sd4jrj455   -  person 0___________    schedule 02.07.2021
comment
@0___________, Не совсем понял, о чем ты? Это просто подтверждение того, что целочисленное продвижение действительно происходит?   -  person ikegami    schedule 02.07.2021
comment
Ответьте на здесь дать какое-либо представление?   -  person Steve Summit    schedule 02.07.2021
comment
@ikegami Подумайте о точности и C, которые предшествовали различию между signed int и unsigned int, вызывая UB, если печатались с %x или подобным. Например, я почти уверен, что %x не является UB, когда передается signed int в C89.   -  person Andrew Henle    schedule 02.07.2021


Ответы (4)


Рассмотрим этот пример:

#include <stdio.h>
int main(void)
{
    unsigned short x = 32770;
    printf("%d\n", x) ;  // (1)
    printf("%u\n", x) ;  // (2)
}

В типичной 16-разрядной реализации продвижение аргумента по умолчанию принимает значение от unsigned short до unsigned int, тогда как в типичной 32-разрядной реализации unsigned short становится int.

Таким образом, в 16-битной системе (1) является UB и (2) правильным, но в 32-битной системе (1) является правильным и (2) можно обсуждать, правильно ли это или UB.

Использование %hu для печати x работает во всех системах, и вам не нужно думать об этих проблемах.

Аналогичный пример можно построить для char в системах с sizeof(int) == 1.

person M.M    schedule 01.07.2021
comment
@ikegami см. 7.21.6.1/8. Спецификатор u определил поведение только для аргумента unsigned int. Стандарт разрешает компилятору использовать разные регистры для передачи int, чем unsigned int; когда я говорю, что это можно обсудить, я в основном имею в виду, что люди делают вид, что стандарт неисправен в этом вопросе, и что они могут уйти от передачи подписанных целых чисел в %u - person M.M; 02.07.2021
comment
Строка @ikegami (2) в моей программе предоставляет аргумент int в типичной 32-битной системе. Вы даже процитировали правило в своем предыдущем комментарии: 32-битное int может представлять все значения 16-битного unsigned short - person M.M; 02.07.2021
comment
Вы должны добавить свой ответ здесь Никто об этом не упоминал! - person ikegami; 02.07.2021
comment
@ikegami, возможно, мод мог бы объединить вопросы, idk - person M.M; 02.07.2021
comment
Я никогда не видел, чтобы это происходило в SO. Мы можем закрыть как дубликат, что мне и придется сделать, хотя я думаю, что это настоящий ответ, а его там нет. - person ikegami; 02.07.2021
comment
Я видел, как это произошло - person M.M; 02.07.2021
comment
Если мы закроем это как дубликат, я поддержу М.М. скопировать и вставить этот отличный ответ туда. - person Steve Summit; 02.07.2021

Это для обратной совместимости.

В черновой версии стандарта C89 печать подписанного int , short или char со спецификатором %xformat не является неопределенным поведением:

d, i, o, u, x, X Аргумент int преобразуется в десятичное число со знаком ( d или i ), восьмеричное число без знака ( o ), десятичное число без знака ( u ) или шестнадцатеричное представление без знака ( x или X ); буквы abcdef используются для преобразования x, а буквы ABCDEF — для преобразования X. Точность определяет минимальное количество отображаемых цифр; если преобразуемое значение может быть представлено меньшим количеством цифр, оно будет дополнено начальными нулями. Точность по умолчанию равна 1. Результат преобразования нулевого значения с явно заданной нулевой точностью не содержит символов.

Похоже, это свидетельствует о том, что предварительно стандартизированный C с использованием спецификаторов формата, таких как %x для значений signed, был существующей практикой, поэтому, вероятно, существовала ранее существовавшая кодовая база с использованием модификаторов длины h и hh.

Без модификаторов длины h и hh значения signed char с битовым шаблоном 0xFF будут напечатаны в 32-битной системе int как 0xFFFFFFFF, если они будут напечатаны с помощью простого спецификатора формата %X.

person Andrew Henle    schedule 02.07.2021
comment
@ikegami Найдите это в стандарте C89: port70.net/ ~nsz/c/c89/c89-draft.html#4.9.6.1 - person Andrew Henle; 02.07.2021
comment
Эта ошибка исправлена ​​в новой версии Аргумент unsigned int преобразуется в восьмеричное беззнаковое (o), десятичное беззнаковое (u) или шестнадцатеричное беззнаковое представление (x или X). См. также printf - person ikegami; 02.07.2021
comment
Текст C89 явно неисправен, поскольку он говорит только об аргументе int, тогда как на самом деле аргумент может быть unsigned int. Если вы, конечно, не хотите утверждать, что printf("%u", 1u); является UB в C89. - person M.M; 02.07.2021
comment
Я отредактировал комментарий, чтобы включить фактическую стандартную цитату. Вы используете устаревшую ссылку, которая явно содержит ошибки, как сказал М.М. И да, я действительно доверяю cppreference больше, чем вам :) - person ikegami; 02.07.2021
comment
Это не стандарт C89. Это какой-то сквозняк. Фактический стандарт ANSI/ISO 9899-1990 в пункте 7.9.6.1 (не 4.9.6.1!) имеет одну запись для «d, i», в которой говорится «Аргумент int…», а другую — для «o, u, x, X». ", в котором говорится: "Аргумент unsigned int..." - person Eric Postpischil; 02.07.2021
comment
@EricPostpischil Хорошо, тогда я предполагаю, что у тебя есть лучшая ссылка? - person Andrew Henle; 02.07.2021
comment
@ Эндрю Хенле, доверяй, но проверяй, что я и сделал. В последней спецификации (ну, N2176) четко сказано unsigned int - person ikegami; 02.07.2021
comment
У меня есть PDF-скан печатного стандарта. Он больше не доступен по URL-адресу, по которому я его получил. - person Eric Postpischil; 02.07.2021
comment
@AndrewHenle Я сохранил копию этой ссылки несколько лет назад, и в ней не упоминается hh в разделе 4.9.6.1. (Сайт в настоящее время не обслуживает всю страницу для меня, он достигает 30% и останавливается) - person M.M; 02.07.2021
comment
дерьмо, но вы сказали, что это для обратной совместимости, что я полностью пропустил. Кто-нибудь действительно имеет доступ к C89? - person ikegami; 02.07.2021
comment
@EricPostpischil Я обновлю, чтобы отразить, что я цитирую черновик. Поскольку черновик является доказательством того, что использование %x для целых чисел со знаком существовало для предварительно стандартизированного C, поэтому флаги h и hh будут сохранены для обратной совместимости. - person Andrew Henle; 02.07.2021
comment
@ikegami Это первая строчка. ;-) - person Andrew Henle; 02.07.2021
comment
@EricPostpischil ISO C90 и ANSI C89 - это разные документы, мне интересно, произошло ли это разделение d, i из беззнаковых спецификаторов между черновиком и ANSI C89 или между C89 и C90. Я подозреваю первое, так как я слышал, что C89 и C90 идентичны, за исключением нумерации абзацев. - person M.M; 02.07.2021

Что касается конкретно спецификатора hh, он был явно добавлен в C99, чтобы использовать печать всех типов фиксированного размера по умолчанию из stdint.h/inttypes.h. C99 делает типы int_leastn_t от 8 до 64 обязательными, поэтому возникла необходимость в соответствующих спецификаторах формата.

Из обоснования C99 5.10, §7.19.6.1 (fprintf):

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

§7.19.6.2 (fscanf):

Новая функция C99: модификаторы длины hh и ll были добавлены в C99. ll поддерживает новый тип long long int. hh добавляет возможность обрабатывать символьные типы так же, как и все другие целочисленные типы; это может быть полезно при реализации макросов, таких как SCNd8 в <inttypes.h> (см. 7.18).

До C99 для печати целочисленных типов использовались только d, h и l. В C99 обычная реализация могла бы, например, определить спецификаторы inttypes.h как:

#define SCNi8   hh
#define SCNi16   h
#define SCNi32   d
#define SCNi64  ll

И теперь продвижение аргумента по умолчанию становится головной болью реализации printf/scanf, а не реализации inttypes.h.

person Lundin    schedule 02.07.2021
comment
В процитированном вами отрывке говорится, что он был добавлен для scanf (и не для printf, как вы утверждаете), и очень легко понять, почему он нужен для scanf (где такое же продвижение не происходит, поскольку указатели передаются как аргументы). Это не отвечает на вопрос. - person ikegami; 02.07.2021
comment
@ikegami C99 стремился сделать printf и scanf согласованными. Например, они исправили спецификатор %lf для printf. После исправления, что они добавят новые спецификаторы в scanf, но не в printf? К счастью, они этого не сделали. Нравится вам это или нет, но было причиной того, что hh был добавлен в язык. - person Lundin; 02.07.2021
comment
Это было бы ответом (хотя утверждение, что это для согласованности, противоречит тому факту, что они действительно необходимы с printf в некоторых ситуациях, как указано в предыдущих ответах). Я прокомментировал то, что вы ответил. - person ikegami; 02.07.2021
comment
@ikegami Добавлена ​​ссылка на printf. - person Lundin; 02.07.2021

Они предназначены не для printf() использования, а для scanf() возможности использовать ссылки на short целые числа и char целые числа. Для единообразия и полноты они принимаются для функций printf(), но они неразличимы, поскольку параметры vaarg функции printf() повышаются до int для всех параметров, которые имеют целочисленные значения типов short и char. Так что они эквивалентны в printf(), но не в scanf() и друзьях.

person Luis Colorado    schedule 03.07.2021