Портативный способ получить int32_t, переданный в функцию с переменным числом аргументов

7.16.1.1 2 описывает va_arg следующим образом (выделено мной):

Если фактического следующего аргумента нет или если тип несовместим с типом фактического следующего аргумента (как продвигается в соответствии с продвижением аргумента по умолчанию), поведение не определено, за исключением следующих случаев. :

  • один тип — целочисленный тип со знаком, другой тип — соответствующий целочисленный тип без знака, и значение может быть представлено в обоих типах;
  • один тип является указателем на void, а другой — указателем на символьный тип.

Теперь, насколько я понимаю, и кажется, что 6.5.2.2 (вызовы функций) не противоречит мне, хотя я могу ошибаться, акции по умолчанию:

  • char либо int, либо unsigned (указывается реализация)
  • signed char to int
  • unsigned char to unsigned
  • short to int
  • unsigned short to unsigned
  • float to double

Это все прекрасно и модно, когда вы знаете точные базовые типы, переданные в va_list (за исключением char, который AFAIK невозможно получить переносимым способом, потому что его подписанность указана реализацией).

Ситуация усложняется, когда вы ожидаете, что типы из <stdint.h> будут переданы в ваш va_list.

  • int8_t и int16_t, за вычетом логических ограничений, гарантированно будут повышены или уже будут иметь тип int. Однако очень сомнительно полагаться на мои первоначальные «логические» предельные наблюдения, поэтому я прошу вашего (и стандарта) подтверждения этого вывода (я могу упустить некоторые крайние случаи, я даже не в курсе).
  • то же самое верно для uint8_t и uint16_t, за исключением того, что базовый тип unsigned
  • int32_t может быть или не быть повышен до int. Оно может быть больше, меньше или точно такое же, как int. То же самое верно для uint32_t, но для unsigned. Как переносимо получить int32_t и uint32_t, переданные va_list? Другими словами, как определить, был ли int32_t (uint32_t) повышен до int(unsigned)? Другими словами , как определить, следует ли использовать va_arg(va, int) или va_arg(va, int32_t) для получения int32_t, переданного в функцию с переменным числом аргументов, без вызова неопределенного поведения на любой платформе?
  • Я считаю, что те же самые вопросы действительны для int64_t и uint64_t.

Это теоретический (относящийся только к стандарту) вопрос с предположением, что все типы точной ширины в <stdint.h> присутствуют. Меня не интересуют ответы типа «что верно на практике», потому что я считаю, что уже знаю их.

ИЗМЕНИТЬ

Одна идея, которую я имею в виду, состоит в том, чтобы использовать _Generic для определения базового типа int32_t. Я не уверен, как именно вы будете его использовать. Я ищу лучшие (более простые) решения.


person MarkWeston    schedule 28.12.2017    source источник
comment
Маловероятно, что int32_t меньше int. Это была бы довольно странная реализация, так как 64-битное int означало бы, что short является либо 16-битным, либо 32-битным, оставляя другую ширину без соответствующего собственного типа.   -  person DevSolar    schedule 28.12.2017
comment
@DevSolar: Существуют действительно византийские архитектуры, особенно во встраиваемом пространстве. Тем не менее, система, не повышающая int32_t до int_fast32_t to, была бы особенно странной. Лично я бы не стал заморачиваться и просто согласился бы на утверждение PRIu32, если бы не было очевидного решения.   -  person doynax    schedule 28.12.2017
comment
Одним из не очень сумасшедших решений было бы использование autoconf для обнаружения поведения компилятора и макроса или двух.   -  person rodrigo    schedule 28.12.2017
comment
Акции не учитывают подписанность. В соответствии с 6.3.1.2 2, «Если int может представлять все значения исходного типа (с ограничениями по ширине для битового поля), значение преобразуется в int< /б>; в противном случае он преобразуется в целое число без знака». Таким образом, uint8_t станет int, а не unsigned int.   -  person Eric Postpischil    schedule 28.12.2017
comment
unsigned char и unsigned short повышаются до int, если они могут представлять все значения исходного типа, и unsigned int в противном случае.   -  person T.C.    schedule 29.12.2017


Ответы (3)


#define IS_INT_OR_PROMOTED(X) _Generic((X)0 + (X)0, int: 1, default: 0)

Использование:

int32_t x = IS_INT_OR_PROMOTED(int32_t) ? 
              (int32_t)va_arg(list, int) : 
              va_arg(list, int32_t);

С gcc на моем ПК макрос возвращает 1 для int8_t, int16_t и int32_t и 0 для int64_t.

С gcc-avr (16-битная цель) макрос возвращает 1 для int8_t и int16_t и 0 для int32_t и int64_t.

Для long макрос возвращает 0 независимо от того, является ли sizeof(int)==sizeof(long).

У меня нет целей с 64-битными ints, но я не понимаю, почему это не сработает на такой цели.

Я не уверен, что это будет работать с действительно патологическими реализациями На самом деле я почти уверен, что теперь это будет работать с любой соответствующей реализацией.

person n. 1.8e9-where's-my-share m.    schedule 28.12.2017
comment
Честно говоря, я не понимаю, почему это не работает с любой соответствующей реализацией. Это может быть ответом, если кто-то не может дать предостережение. - person MarkWeston; 28.12.2017
comment
Накопите за это голоса, люди (если кто-то не найдет недостаток). Эзотерические знания, применяемые для решения неясной, но актуальной проблемы, заслуживают признания. - person Eric Postpischil; 29.12.2017
comment
Это отлично и устраняет одну из последних проблем с блокировкой, с которыми я столкнулся при более широком использовании типов <inttypes.h>. - person BeeOnRope; 30.12.2017

На самом деле нет хорошего способа сделать это. Я считаю каноническим ответом «не делай этого». Помимо отказа от передачи таких типов в качестве аргументов функциям с переменным числом переменных, избегайте даже использования их в качестве «переменных» и используйте их только в качестве «хранилища» (в массивах и структурах, которые существуют в больших количествах). Конечно, легко сделать ошибку и передать такой элемент/член в качестве аргумента вашей функции с переменным числом аргументов, так что это не очень приятно.

Ваша идея с _Generic работает только в том случае, если эти типы не определены с специфическими для реализации расширенными целочисленными типами, о которых ваш код не знает.

Существует ужасный, но допустимый подход, включающий передачу va_list в vsnprintf с правильным макросом "PRI*", затем синтаксический анализ целого числа из строки, но после этого список находится в состоянии, когда вы не можете использовать его снова, поэтому, если работает только для последнего аргумента.

Ваш лучший выбор, вероятно, попытаться найти формулу для «продвигается ли этот тип по акциям по умолчанию?» Вы можете легко запросить, превышает ли максимальное значение типа INT_MAX или UINT_MAX, но это все равно не поможет формальной правильности, если есть ложный расширенный целочисленный тип с тем же диапазоном.

person R.. GitHub STOP HELPING ICE    schedule 28.12.2017
comment
В чем проблема с ложным расширенным целочисленным типом? Если значения int32_t являются подмножеством значений int, int32_t повышается до int, поэтому работает va_arg(list, int). Если нет, int32_t не продвигается, поэтому va_arg(list, int32_t) работает. Что мне не хватает? - person Eric Postpischil; 28.12.2017
comment
@EricPostpischil: если int 16-битное, то va_arg(list, int) будет UB; если int является 64-битным, то va_arg(list, int32_t) является UB; в любом случае вы не можете написать код, который работает каждый раз. - person rodrigo; 28.12.2017
comment
@rodrigo: код будет INT_MIN <= INT32_MIN && INT32_MAX <= INT_MAX ? va_arg(list, int) : va_arg(list, int32_t) (или эквивалентное решение с использованием #if). Итак, да, мы можем написать исходный код, который использует int, когда int32_t повышается до int, и использует int32_t, когда он не повышается до int. Но Р.. кажется, указывает на то, что с этим есть какая-то проблема. - person Eric Postpischil; 28.12.2017
comment
@R.. Если INT32_MAX равен >, < или == до INT_MAX, разве это не гарантирует, что ранг int32_t выше, ниже или равен рангу int? Где предостережение? - person MarkWeston; 28.12.2017
comment
Вы можете использовать va_copy, чтобы обойти проблему невозможности использования списка аргументов впоследствии. - person fuz; 28.12.2017
comment
@MarkWeston: когда диапазон int32_t и диапазон int равны, это не делает рейтинг конверсии равным. Эти два пункта усложняют ситуацию: Ранг long long int должен быть больше, чем ранг long int, который должен быть больше, чем ранг int, который должен быть больше, чем ранг short int, который должен быть больше чем ранг знакового char. и ранг любого стандартного целочисленного типа должен быть выше ранга любого расширенного целочисленного типа той же ширины. - person Ben Voigt; 28.12.2017
comment
@fuz: Но тогда вы не можете пройти мимо неприятного аргумента, чтобы прочитать следующий. - person R.. GitHub STOP HELPING ICE; 29.12.2017

Что касается решения #if и <limits.h>, я нашел это (6.2.5.8):

Для любых двух целочисленных типов с одинаковым знаком и разным целочисленным рангом преобразования (см. 6.3.1.1) диапазон значений типа с меньшим целочисленным рангом преобразования является поддиапазоном значений другого типа.

А в 6.3.3.1 говорится (выделено мной):

Каждый целочисленный тип имеет целочисленный ранг преобразования, определяемый следующим образом:

  • Никакие два целых типа со знаком не должны иметь одинаковый ранг, даже если они имеют одинаковое представление.
  • Ранг целочисленного типа со знаком должен быть выше ранга любого целочисленного типа со знаком с меньшей точностью.
  • Ранг long long int должен быть выше ранга long int, который должен быть выше ранга int, который должен быть выше ранга short int, который должен быть выше ранга signed char.
  • Ранг любого целочисленного типа без знака должен быть равен рангу соответствующего целочисленного типа со знаком, если таковой имеется.
  • Ранг любого стандартного целочисленного типа должен быть выше ранга любого расширенного целочисленного типа той же ширины.
  • Ранг char должен быть равен рангу signed char и unsigned char.
  • Ранг _Bool должен быть меньше ранга всех других стандартных целочисленных типов.
  • Ранг любого перечисляемого типа должен быть равен рангу совместимого целочисленного типа (см. 6.7.2.2).
  • Ранг любого расширенного целочисленного типа со знаком относительно другого расширенного целочисленного типа со знаком с той же точностью определяется реализацией, но по-прежнему подчиняется другим правилам определения ранга целочисленного преобразования.
  • Для всех целочисленных типов T1, T2 и T3, если T1 имеет больший ранг, чем T2, а T2 имеет больший ранг, чем T3, то T1 имеет больший ранг, чем T3.

И вот что говорит 6.5.2.2 6 (выделено мной):

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

  • один продвигаемый тип является целочисленным типом со знаком, другой продвигаемый тип является соответствующим целочисленным типом без знака, и значение может быть представлено в обоих типах;

  • оба типа являются указателями на квалифицированные или неквалифицированные версии символьного типа или пустоты.

Основываясь на этих наблюдениях, я склонен полагать, что

#if INT32_MAX < INT_MAX
    int32_t x = va_arg(va, int);
#else
    int32_t x = va_arg(va, int32_t);

Это связано с тем, что если диапазон int32_t не может содержать диапазон int, то диапазон int32_t является поддиапазоном int, что означает, что ранг int32_t ниже, чем int, и это означает, что целочисленное продвижение выполняется.

С другой стороны, если диапазон int32_t может содержать диапазон int, то диапазон int32_t является диапазоном int или надмножеством диапазона int, и, таким образом, ранг int32_t равен больше или равно рангу int, что означает, что целочисленное продвижение не выполняется.

ИЗМЕНИТЬ

Поправил тест, судя по комментариям.

#if INT32_MAX <= INT_MAX && INT32_MIN >= INT_MIN
    int32_t x = va_arg(va, int);
#else
    int32_t x = va_arg(va, int32_t);

РЕДАКТИРОВАТЬ 2:

Меня сейчас конкретно интересует этот случай:

  • int — 32-битное целое число в дополнении к единице.
  • int32_t — 32-битное целое число с дополнением до двух (расширенный тип)
  • ширина (такая же, как точность?) такая же
  • но потому что «ранг любого стандартного целочисленного типа должен быть больше, чем ранг любого расширенного целочисленного типа с той же шириной». ранг int выше, чем у int32_t
  • это означает, что необходимо выполнить целочисленное повышение от int32_t до int
  • хотя int не может представлять все значения в int32_t (в частности, не может представлять INT32_MIN). Что происходит? Или я что-то упускаю?
person MarkWeston    schedule 28.12.2017
comment
Если int32_t является расширенным целочисленным типом того же размера, что и int, вам потребуется <=, потому что ранг любого стандартного целочисленного типа должен быть больше, чем ранг любого расширенного целочисленного типа той же ширины. - person Ben Voigt; 28.12.2017
comment
@EricPostpischil: Но int32_t может также быть typedef для стандартного целочисленного типа, он не обязательно должен быть расширенным типом... В случае typedef long int int32_t; продвижение не происходит. Следовательно, мой комментарий сформулирован в условном выражении. - person Ben Voigt; 28.12.2017
comment
@BenVoigt: я согласен, случай, когда int32_t равен long int, кажется проблематичным. - person Eric Postpischil; 28.12.2017