Функция Variadic (va_arg) не работает с float, а printf делает? В чем разница?

У меня просто сложилась аналогичная ситуация, как в этом вопросе, с двух лет:

Функция 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) , как и вы.