Порядок аргументов printf игнорируется - PullRequest
1 голос
/ 22 октября 2019

Я выполняю следующий фрагмент кода:

#include <stdio.h>
int main() {
   printf("%f %d\n", 42, 3.14);
}

Который, к моему удивлению, отображает:

3.140000 42

Компилятор (gcc 8.3.0 в дистрибутиве на основе Debian)предупреждает меня о порядке аргументов:

test.c: In function ‘main’:
test.c:3:13: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
printf("%f %d\n", 42, 3.14);
        ~^        ~~
        %d
test.c:3:16: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘double’ [-Wformat=]
printf("%f %d\n", 42, 3.14);
           ~^         ~~~~
           %f

Может ли душа, более просвещенная, чем моя, объяснить мне такое поведение? В спецификации я не нашел ничего такого, что могло бы объяснить это.

Ответы [ 2 ]

7 голосов
/ 22 октября 2019

На вашей машине так получилось, что стандартное соглашение о вызовах диктует, что аргументы с плавающей точкой передаются в функции в отдельной области от целочисленных и других аргументов. Ваша строка формата printf интересуется первым аргументом с плавающей точкой и первым аргументом без плавающей точки, и находит оба из них в ожидаемом месте, даже если вы передали их в неправильном порядке. Это не то, на что вы, конечно, должны рассчитывать;он может перестать работать, если вы скомпилируете для другой машины, или с небольшими изменениями в вашем коде.

1 голос
/ 24 октября 2019

Добавление подробностей в ответ @hobbs для архитектуры x86-64. Соглашение о вызове функции x86-64 заключается в том, что первые шесть целочисленных или указательных параметров передаются в регистрах

% rdi,% rsi,% rdx,% rcx,% r8 и% r9

, тогда как первые шестнадцать параметров с плавающей запятой передаются в регистрах

% xmm0 -% xmm15

В этом случае первый параметр printf является строкой форматакоторый является указателем, поэтому он будет передан в % rdi , второй аргумент является целым числом, поэтому он будет передан в% rsi. Третий аргумент является числом с плавающей запятой, поэтому он будет передан в регистр% xmm0.

Внутри printf первого аргумента строка формата будет считана из% rdi, теперь она обработает строку формата и найдет "% f ", поэтому он будет читать% xmm0, потому что там, где должен находиться первый аргумент с плавающей точкой, он находит« 3.14 »в% xmm0 и печатает его правильно, затем снова просматривает строку формата и находит% d, поэтому он читаетрегистр% esi, который будет иметь второй целочисленный аргумент, находит там 42 и печатает его правильно.

Поскольку существует шесть регистров для передачи целых чисел / указателей и 16 для передачи плавающих точек, следующее также работает

int main() {
   printf("%d %d %d %d %d %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n",
           1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0, 40, 41, 42,
           43,44);
}

Выход: 40 41 42 43 44 1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000 9.000000 10.000000 11.00000000 12.000000 13.000000 14.000000 15.000000 16.000000

...