У меня просто сложилась аналогичная ситуация, как в этом вопросе, с двух лет:
Функция Variadic (va_arg) не работает с float?
Говорят, что проблема заключается в том, чтобы продвигать float в два раза, когда мы называем такие вещи, как
va_arg(arg, float)
Мой вопрос находится в конце этого сообщения, но сначала давайте посмотрим на ответ @ Jack ниже вопроса, связанного выше:
#include #include void foo(int n, ...) { va_list vl; va_start(vl, n); int c; double val; for(c = 0; c < n; c++) { val = va_arg(vl, double); printf("%f\n", val); } va_end(vl); } int main(void) { foo(2, 3.3f, 4.4f); return 0; }
Выход:
3.300000 4.400000
Теперь, если мы изменим val = va_arg(vl, double)
на val = va_arg(vl, float)
, мы получим (по крайней мере, в MSVS 2012):
36893488147419103000.000000 2.162500
Пойдем теперь в мой вопрос.
В этом разделе: C / C ++ va_list не возвращает аргументы должным образом, большинство проголосовавших ответов и его комментарий говорит, что printf
также продвигает float
в double
.
Но в чем разница? Если оба из них продвигают float
в double
, почему printf
правильно записывает значения, а va_arg
дает нам таких носовых деmonoв?
Это не printf
который способствует double
аргумента float, это компилятор, который делает это. Другими словами, к моменту, когда ваш va_arg
или printf
или любая другая функция с переменным числом аргументов получает контроль, все float
s уже повышены до double
s; исходные float
недоступны для извлечения.
Разница между printf
и va_arg
заключается в том, что printf
следует правилам, установленным стандартом, и запрашивает параметр продвигаемого типа (т.е. double
), когда он видит соответствующий формат в строке формата. Следовательно, он успешно получает double
с продвинутым значением float
в нем и производит желаемый результат.
С другой стороны, когда va_arg
вызывает val = va_arg(vl, float)
он игнорирует правило продвижения и получает недопустимое представление взамен.
printf
не принимает аргументы типа float
.
Например, спецификатор формата "%f"
требует аргумента типа double
. "%Lf"
требует аргумента типа long double
. Нет формата, для которого требуется аргумент типа float
(который будет продвигаться в double
, а не только с помощью printf
, а просто из-за семантики вызовов вариационных функций).
Поэтому, предполагая, что printf
реализован в C и что он использует механизмы
для чтения своих аргументов, не существует обращения к va_arg()
для типа float
при реализации printf
.
Любая вариационная функция, которая пытается вызвать va_arg()
для типа float
будет иметь неопределенное поведение, поскольку для такой функции не может быть аргументов float
. printf
работает, потому что он этого не делает.
Аргументы для переменных функций аргументов получают специальные правила продвижения.
Единственное, что уместно здесь, это то, что передача float в качестве переменного аргумента получает двойной. Это означает, что вы не можете извлечь аргумент как float, поскольку он передан как double. Это делается компилятором, это не имеет ничего общего с printf
.
Это означает, что код val = va_arg(vl, float)
недействителен, поскольку аргумент не является float, он является двойным. Если вам действительно нужно иметь дело с переданными в значениях значениями float, в лучшем случае вы можете сделать
float val = (float) va_arg(vl, double)
Обратите внимание, что спецификатор %f
для printf ожидает аргумент типа double
, а не float
Но в чем разница? Если оба из них продвигают
float
вdouble
, почемуprintf
правильно записывает значения, аva_arg
дает нам таких носовых деmonoв?
Нет никакой разницы, кроме факта (указанного в самом вопросе), что printf
закодирован таким образом, чтобы рассматривать float
как double
. Другими словами, где-то внутри printf
, когда в строке формата содержится информация о том, что должно быть число с плавающей запятой, функция выполняет va_arg(vl, double)
, как и вы.