2

Consider the following code from C Primer Plus:

#include <stdio.h>

int main()
{
    float f = 7.0f;
    float g = 8.0f;

    printf("%d %d\n", f, g);        // wrong kind of values

    return 0;
}

using the clang compiler produces the following output which is an expected result by the way. The compiler rightfully complains about the float type arguments that are assigned to wrong format specifiers:

~$ clang badcount.c -o badcount
badcount.c:11:23: warning: format specifies type 'int' but the argument has type 'float' [-Wformat]
printf("%d %d\n", f, g);        // wrong kind of values
        ~~        ^
        %f
badcount.c:11:26: warning: format specifies type 'int' but the argument has type 'float' [-Wformat]
printf("%d %d\n", f, g);        // wrong kind of values
           ~~        ^
           %f
2 warnings generated.

however compiling the same code on gcc 8.2.0 will produce the following output:

~$ gcc-8 badcount.c -fsingle-precision-constant -o badcount -Wall
badcount.c: In function 'main':
badcount.c:11:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
 printf("%d %d\n", f, g);        // wrong kind of values
         ~^        ~
         %f
badcount.c:11:17: warning: format '%d' expects argument of type 'int', but argument 3 has type 'double' [-Wformat=]
 printf("%d %d\n", f, g);        // wrong kind of values
            ~^        ~
            %f

even though f and g are of type float and even though an f suffix is used to explicitly make literal values of type float, gcc will still promote them into double types. I even tried the following code but still gcc ignores explicit float types and promote them into double. I looked up the gcc documentation but it looks like there are no options to prevent automatic promotion of literal floats to literal doubles, and gcc implicitly does this, and ignores f suffixes on literals.

    printf("%d %d\n", f, 45.9f);

In the above code 45.9f will still be promoted to type double even though I have implicitly made it float.

Are there any options or solutions to prevent this? Or is this intended by design in gcc?

Edit: I forgot to put this code that I tried, using an explicit cast to type float doesn't prevent this either:

printf("%d %d\n", f, (float)(45.9f));

not even this:

float f = (float)7.0f;

It seems gcc doesn't care about explicit castings either, and will promote floats to double all the same.

ilgaar
  • 804
  • 1
  • 12
  • 31
  • I know for float and double we use %f, but that is not the point of this question. – ilgaar Nov 30 '18 at 22:29
  • not even an explicit cast prevents gcc from promoting float to double, I forgot to mention this in my question. – ilgaar Nov 30 '18 at 22:37
  • 9
    Becuase C11 draft standard n1570, *6.5.2.2 Function calls*, paragraphs 6 and 7 specify that a varargs argument is promoted from `float` to `double`. – EOF Nov 30 '18 at 22:41
  • then `gcc-8 badcount.c -Wall -o badcount -std=c99` should not promote the floats to doubles. and `clang badcount.c -o badcount -std=c11` should promote the floats to doubles, but they don't. – ilgaar Nov 30 '18 at 22:46
  • Float argument is automatically promoted to double in `printf` function, so it doesn't matter whether constant is declared as float or double. Automatic conversions float->double can be tracked with `-Wdouble-promotion`. – gudok Nov 30 '18 at 22:47
  • 2
    @ilgaar: The rule is the same in C90, C99, and C11. Variadic arguments of type `float` are promoted to `double`, and variadic arguments of integer types narrower than `int` are promoted to `int` or to `unsigned int`. The `-std=...` argument should have no effect on this. – Keith Thompson Nov 30 '18 at 22:50
  • @EOF: You should post that as an answer (or possibly close as a duplicate if you can find the same question elsewhere). – Keith Thompson Nov 30 '18 at 22:50
  • @gudok Then why `printf` will not promote floats to double using the `clang` complier? Is standard different in `clang` than `gcc` in this particular problem? – ilgaar Nov 30 '18 at 22:53
  • @Keith Thompson Then why doesn't this happen in `clang`? Isn't `clang` following the standard in this particular occasion? – ilgaar Nov 30 '18 at 22:57
  • 3
    Who says clang doesn't? – Marc Glisse Nov 30 '18 at 23:03
  • 5
    @ilgaar: For arguments corresponding to `...` in the parameters, both compilers promote `float` to `double`. The difference in the error messages is merely a choice about whether to describe the mismatch between `int` and either the original argument type (`float`) or the promoted argument type (`double`). – Eric Postpischil Nov 30 '18 at 23:04
  • You are right I tried the `clang badcount.c -o badcount -Weverything` and it shows that `clang` also promotes floats to double as well, and it is merely a difference in how both compiler present warnings. – ilgaar Nov 30 '18 at 23:10

1 Answers1

5

Both compilers respect the standard and generate equivalent code.

Let's check what your code gets translated to at the assembly level.

clang -O3 -fomit-frame-pointer -S ctest.c -o ctest1.s

LCPI0_0:
    .quad   4619567317775286272
LCPI0_1:
    .quad   4620693217682128896

_main:
    pushq   %rax
    leaq    L_.str(%rip), %rdi
    movsd   LCPI0_0(%rip), %xmm0
    movsd   LCPI0_1(%rip), %xmm1
    movb    $2, %al
    callq   _printf
    xorl    %eax, %eax
    popq    %rcx
    retq

L_.str:
    .asciz  "%d %d\n"

gcc -O3 -S ctest.c -o ctest2.s

lC2:
    .ascii "%d %d\12\0"

_main:
    leaq    lC2(%rip), %rdi
    subq    $8, %rsp
    movl    $2, %eax
    movsd   lC0(%rip), %xmm1
    movsd   lC1(%rip), %xmm0
    call    _printf
    xorl    %eax, %eax
    addq    $8, %rsp
    ret

lC0:
    .long   0
    .long   1075838976
lC1:
    .long   0
    .long   1075576832

(Some labels and assembler directives removed for brevity)

The conversion from float to double is performed in both cases. The doubles were actually pre-computed at compile time, and are then loaded into SSE registers with movsd.

The difference you observe is simply a choice in the way the compiler reports the error. Clang displays the type before the implicit conversion, and gcc, the type post conversion. But in both cases, the arguments passed to printf are of type double.

Community
  • 1
  • 1