When compiling with c99 option in TDM-GCC 5.1.0, printf %lf output is different than expected

Asked 1 years ago, Updated 1 years ago, 134 views

If you compile TDM-GCC 5.1.0 with c99 as shown in gcc-std=c99-pedantic test.c, the output of %lf will be 0.0000.

Source code of interest:

#include<stdio.h>

int main(void){
    double v=3.1415926;
    printf("%f\n", v);
    printf("%lf\n",v);
}

Run Results (compiled with c99 option):

3.141593
0.000000

On the other hand, compiling with no option specified (gcc test.c) results as expected.

Run Results (compiled without c99 option):

3.141593
3.141593

screen shot

The use of %lf in printf should be appropriate for C99, which is a strange result.

In the previous version of GCC (MinGwgcc 3.2), %lf was available when using the c99 option, so I think it is a unique bug in this GCC, but I am looking for a way to avoid it for the time being (until bug fixes).

C99There is no problem with compiling without the option, so I think we can avoid it in some way.

c gcc c99

2022-09-30 21:23

4 Answers

MinGW based on TDM-GCC has two types of stdio: MinGW stdio and MSVCRT stdio. It can be accessed under the names _mingw_printf() and _msvcrt_printf().

#include<stdio.h>

int main(void){
    double v=3.1415926;
    printf("%f\n", v);
    printf("printf(): %lf\n", v);
    __mingw_printf("_mingw_printf(): %lf\n", v);
    __msvcrt_printf("_msvcrt_printf(): %lf\n", v);
}

MSVCRT stdio is MSVCRT.dll, but this is from Visual C++ 6.0 released in 1998, so it has nothing to do with C99 or C11.Format Specification Fields: printf and wprintf Functions has a document with %zu

does not exist either.

Now, MinGW stdio and MSVCRT stdio switch operations with the value __USE_MINGW_ANSI_STDIO as described below in <stdio.h>.

#if_USE_MINGW_ANSI_STDIO
/*
 * User has expressed a preference for C99 conformance...
 */
(omitted)

__mingw_stdio_redirect__
int printf(const char*__format,...)
{
  register int__retval;
  __buildtin_va_list__local_argv;_buildtin_va_start(_local_argv,__format);
  __retval=_mingw_vprintf(_format,__local_argv);
  __buildin_va_end(_local_argv);
  return__retval;
}

(omitted)

# else
/*
 * Default configuration: Simply direct all calls to MSVCRT...
 */
_CRTIMP int__cdecl_MINGW_NOTHROW fprintf(FILE*, const char*,...);
_CRTIMP int__cdecl_MINGW_NOTHROW printf(const char*,...);
_CRTIMP int__cdecl_MINGW_NOTHROW sprintf(char*, const char*,...);
_CRTIMP int__cdecl_MINGW_NOTHROW vfprintf (FILE*, const char*, __VALIST);
_CRTIMP int__cdecl_MINGW_NOTHROW vprintf(const char*,__VALIST);
_CRTIMP int__cdecl_MINGW_NOTHROW vsprintf(char*, const char*, __VALIST);

#endif

where __USE_MINGW_ANSI_STDIO is set to <_mingw.h>.

/*Activation of MinGW specific extended features:
 */
# ifndef__USE_MINGW_ANSI_STDIO
/*
 * If user didn't specify it explicitly...
 */
# if defined__STRICT_ANSI__||defined_ISOC99_SOURCE\
   ||  defined_POSIX_SOURCE||defined_POSIX_C_SOURCE\
   ||  defined_XOPEN_SOURCE||defined_XOPEN_SOURCE_EXTENDED\
   ||  defined_GNU_SOURCE||defined_BSD_SOURCE\
   ||  defined_SVID_SOURCE
   /*
    * but where any of these source code qualifiers are specified,
    * then Assume ANSI I/O standards are preferred over Microsoft's...
    */
#  define__USE_MINGW_ANSI_STDIO1
# else
   /*
    * otherwise use whatver_MINGW_FEATURES_specifications...
    */
#  define__USE_MINGW_ANSI_STDIO(__MINGW_FEATURES_&__MINGW_ANSI_STDIO_)
# endif
#endif

Compilation options -std=c99 and -ansi also define __STRICT_ANSI_ in conjunction with defining __USE_MINGW_ANSI_STDIO.
As a result, MinGW stdio and MSVCRT stdio switch.

If you understand the principles, it's clear.

  • -std=gnu99 (not -std=c99 or -ansi)
  • Specify -D_USE_MINGW_ANSI_STDIO=0
  • Using _msvcrt_printf() instead of
  • printf()

That's about it.

Verified the TDM-GCC source code mingwrt-3.21-mingw32-src.tar.xz where printf() ends up in mingwex/stdio/format.c as defined in mingwex/stdio/format().

 case 'l':
        /*
         * Interpret the argument as explicitly of a
         * `long' or `long' data type.
         */
        if(*fmt=='l')
        {
          /* Modifier is `ll'; data type is `long' sized...
           * Skip the second `l', and set length recordingly.
           */
          ++fmt;
          length = PFORMAT_LENGTH_LLONG;
        }

        else
          /* Modifier is `l'; data type is `long' sized...
           */
          length = PFORMAT_LENGTH_LONG;

#           ifndef_WIN32
          /*
           * Microsoft's MSVCRT implementation uses `l'
           * as a modifier for `long double'; if we don't want
           * to support that, we end this case here...
           */
          state = PFORMAT_END;
          break;

          /* elsewise, we simply fall through...
           */
#       endif

      case 'L':
        /*
         * Identify the approve argument as a `long double',
         * when associated with `%a', `%A', `%e', `%E', `%f', `%F',
         * `%g' or `%G' format specifications.
         */
        stream.flags | = PFORMAT_LDOUBLE;
        state = PFORMAT_END;
        break;

In environments where _WIN32 is not defined, the C99 equivalent l flag is ignored, but in environments where _WIN32 is defined, falling through to the L flag to match MSVCRT operation is considered long double.

Regarding MSVCRT, historically in the 16-bit era, float 32bit, double 64bit, and long double 80bit.Therefore, %lf was used when printf() the long double.I changed it to long double64bit when I turned Windows 95/32bit.As a result, whether the %lf in printf() represents long double or double represents double, the path is 64 bits and has no effect on printf().
However, the MinGW has its own long double set to 96 bits (originally, it should be 64 bits for Visual C++).This can only be described as a specification bug. And as mentioned above, printf() drags the behavior of Visual C++ in the 16-bit era and considers %lf as long double.

To address this discrepancy, printf("%lf\n", (long double)v); must be explicitly cast and correctly argumentated.

Another fundamental solution is to use the gcc compilation option -mlong-double-64.However, you will need to rebuild all runtime, including printf().Or is it faster to rebuild from gcc?

<_mingw.h> contains the following comments:

/*These are defined by the user (or the compiler)
   to specify how identifiers are imported from a DLL.

   __DECLSPEC_SUPPORTED Defined if dlimport attribute is supported.
   __MINGW_IMPORT The attribute definition to specify imported
                                   variables/functions.
   _CRTIMP Asabove. For MS compatibility.
   __MINGW32_VERSION Runtime version.
   __MINGW32_MAJOR_VERSION Runtime major version.
   __MINGW32_MINOR_VERSION Runtime minor version.
   __MINGW32_BUILD_DATE Runtime build date.

   Macros to enable MinGW features which generate from standard MSVC
   compatible behaviour; these may be specified directly in user code,
   activated explicitly, (e.g. by specifying_POSIX_C_SOURCE or Such),
   or by inclusion in __MINGW_FEATURES__:

   __USE_MINGW_ANSI_STDIO Select a more ANSI C99 compatible
                                   implementation of printf() and friends.

It says These are defined by the user or the may be specified directly in user code, so you can specify it.
In the first place, this behavior relies heavily on the internal implementation of MinGW stdio and MSVCRT stdio switching choices, so you should be very careful when you specify it, and if you do it carefully, you should be hesitant.

For example, what happens if you use _POSIX_C_SOURCE, _XOPEN_SOURCE (or _GNU_SOURCE) as pointed out in the previous email?

Please review this response and code carefully.#if_USE_MINGW_ANSI_STDIO will not be achieved by #define either way.


2022-09-30 21:23

From the questioner's comment

I want (TDM-GCC) to work on C99

However, as I commented on the return value of snprintf, standards, specifications, and implementation are different issues.The GCC may be C99 compliant within the compiler's responsibility, but the MinGW and Visual C++ 6.0 that TDM-GCC uses in its running environment should not be C99 compliant.In general, TDM-GCC is not C99 compliant.

If you want to use C99, you should choose a C99 compliant environment.


2022-09-30 21:23

When -std=c99 is specified, is it treated the same as "%Lf"?
printf() is a variable length argument and float is extended to double, so "%lf" was a mistake in the past.
Although C99 now allows "%lf", you don't have to use "%lf".

Compilation results:

Results:

Compilation results:

 d:\tmp>gcc-Wall-pedantic double.c
double.c: In function 'main':
double.c:10:12:warning:unknown conversion type character'L'in format [-Wformat=]
     printf("%%Lf-double:%Lf\n",v);
            ^
double.c:10:12:warning:too many arguments for format [-Wformat-extra-args]
double.c:11:12:warning:format '%f' expectations argument of type 'double', but argument2 has type 'long double' [-Wformat=]
     printf("%%f-(long double):%f\n", (long double)v);
            ^
double.c:12:12:warning:format '%lf' expectations argument of type 'double', but argument 2 has type 'long double' [-Wformat=]
     printf("%%lf-(long double):%lf\n", (long double)v);
            ^
double.c:13:12:warning:unknown conversion type character'L'in format [-Wformat=]
     printf("%%Lf-(long double):%Lf\n", (long double)v);
            ^
double.c:13:12:warning:too many arguments for format [-Wformat-extra-args]

Results:

Note:


2022-09-30 21:23

As noted in Linked Answer of @nekketsuuuuu's comment, apparently the bug (%lf from MinGW 4.8 originating in TDM is long doublee>interpreted as a designation for ).

printf("%lf\n", (long double)v);

The expected value was displayed.(that is, it was interpreted as a designation for long double)
(%Lf which originally means long double works as expected.)

There was information like specifying the __USE_MINGW_ANSI_STDIO macro elsewhere. At least it doesn't seem to work now.
(From Sayuri's answer, if -std=c99, it is already specified.)

MinGW-w64 doesn't seem to have this problem, so switch the compiler or

Using -std=gnu99 instead of -std=c99 seems to be a simple solution.
(However, %zu becomes unavailable)

-std=c99 (even though -std=c11) It seems that switching reference headers when specifying standards is the reason why the behavior is different.
I wish I could switch between them without causing any problems from users, but I think it's difficult because there are many things involved.


2022-09-30 21:23

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.