TDM-GCC 5.1.0 で c99 オプションを指定したコンパイル時、printf の %lf で出力される結果が想定と異なる


TDM-GCC 5.1.0 で以下のソースを `gcc -std=c99 -pedantic test.c` のように `c99`
を指定してコンパイルを実行すると、`%lf` 指定した方の出力が `0.000000` になってしまいます。

**対象のソースコード:**

[code]

    #include <stdio.h>
    
    int main(void){
        double v = 3.1415926;
        printf("%f\n", v);
        printf("%lf\n", v);
    }
    
[/code]

**実行結果 (c99 オプションありでコンパイル):**

[code]

    3.141593
    0.000000
    
[/code]

一方、オプション指定なし(`gcc test.c`)でコンパイルした場合には、期待通りの結果が得られます。

**実行結果 (c99 オプションなしでコンパイル):**

[code]

    3.141593
    3.141593
    
[/code]

![screen shot](https://i.stack.imgur.com/lU5yX.png)

`printf` での `%lf` の使用は C99 では適合なはずなので、おかしな結果です。

以前使用していた古いバージョンの GCC (MinGw gcc3.2) では`c99`オプション使用時に`%lf`は使用できていたので、この GCC
での固有バグではないかと思いますが、当面（バグフィックスされるまで）困るので回避方法を探しています。

`C99` オプションなしでコンパイルした場合は問題ないので、なんらかの方法で回避できるのではないかと思っています。




# `-std=c99`と`-std=gnu99`で動作が変わる理由

TDM-GCCがベースにしているMinGWにはMinGW stdioとMSVCRT
stdioの２種類のstdioが存在します。`__mingw_printf()`と`__msvcrt_printf()`の名前でアクセスできます。

[code]

    #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);
    }
    
[/code]

MSVCRT stdioの実体は`MSVCRT.dll`ですがこれは1998年にリリースされたVisual C++
6.0のものですので、C99やC11といった仕様とは無縁です。[Format Specification Fields: printf and
wprintf Functions](https://msdn.microsoft.com/en-
us/library/aa246400\(v=vs.60\).aspx)に`printf()`のドキュメントがありましたが、[`%zu`](https://msdn.microsoft.com/en-
us/library/aa272936\(v=vs.60\).aspx)という指定も存在しません。

さてMinGW stdioとMSVCRT
stdioは`<stdio.h>`の次の記述により`__USE_MINGW_ANSI_STDIO`の値により動作が切り替わります。

[code]

    #if __USE_MINGW_ANSI_STDIO
    /*
     * User has expressed a preference for C99 conformance...
     */
    （略）
    
    __mingw_stdio_redirect__
    int printf (const char *__format, ...)
    {
      register int __retval;
      __builtin_va_list __local_argv; __builtin_va_start( __local_argv, __format );
      __retval = __mingw_vprintf( __format, __local_argv );
      __builtin_va_end( __local_argv );
      return __retval;
    }
    
    （略）
    
    #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
    
[/code]

ここで`__USE_MINGW_ANSI_STDIO`は`<_mingw.h>`で設定されています。

[code]

    /* 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_STDIO    1
    # else
       /*
        * otherwise use whatever __MINGW_FEATURES__ specifies...
        */
    #  define __USE_MINGW_ANSI_STDIO    (__MINGW_FEATURES__ & __MINGW_ANSI_STDIO__)
    # endif
    #endif
    
[/code]

コンパイルオプション`-std=c99`や`-ansi`を付けると`__STRICT_ANSI__`が定義されることに連動して`__USE_MINGW_ANSI_STDIO`も定義されます。  
その結果、MinGW stdioとMSVCRT stdioとが切り替わります。

# 回避策

原理がわかれば明快です。

  * `-std=gnu99`を指定する（`-std=c99`や`-ansi`を指定しない）
  * `-D__USE_MINGW_ANSI_STDIO=0`を指定する
  * `printf()`ではなく`__msvcrt_printf()`を使用する

ぐらいでしょうか。

# `-std=c99`で使われるMinGW stdioが`%lf`を正しく扱えない原因

TDM-
GCCのソースコード`mingwrt-3.21-mingw32-src.tar.xz`を確認しました。`printf()`は最終的に`mingwex/stdio/pformat.c`で定義されている`__pformat()`に辿り着きます。

[code]

          case 'l':
            /*
             * Interpret the argument as explicitly of a
             * `long' or `long long' data type.
             */
            if( *fmt == 'l' )
            {
              /* Modifier is `ll'; data type is `long long' sized...
               * Skip the second `l', and set length accordingly.
               */
              ++fmt;
              length = PFORMAT_LENGTH_LLONG;
            }
    
            else
              /* Modifier is `l'; data type is `long' sized...
               */
              length = PFORMAT_LENGTH_LONG;
    
    #           ifndef _WIN32
              /*
               * Microsoft's MSVCRT implementation also 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;
    
              /* otherwise, we simply fall through...
               */
    #       endif
    
          case 'L':
            /*
             * Identify the appropriate 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;
    
[/code]

となっていて、`_WIN32`が定義されていない環境ではC99相当の`l`フラグを無視しますが、`_WIN32`が定義されている環境ではMSVCRTと動作を合わせるために`L`フラグへfall
throughすることで`long double`と見なすようになっています。

なお、MSVCRTについてですが、歴史的に16bit時代は`float` 32bit、`double` 64bit、`long double`
80bitとしていました。そのため`long double`を`printf()`する際に`%lf`を使用していました。Windows
95・32bit化の際これを改め`long double` 64bitと変更しました。その結果、`printf()`の`%lf`が`long
double`を表していても`double`を表していてもどの道64bitであり影響がなく`printf()`の使い方としては曖昧になってしまいました。  
しかし **MinGWでは`long double`を独自に96bitとしてしまっています（本来はVisual
C++に合わせて64bitとすべきところ）。これは仕様バグとしか言いようがありません。**
しかも前述のように`printf()`は16bit時代のVisual C++の動作を引きずって`%lf`を`long
double`とみなしてしまっています。

この食い違いに対処するには`printf("%lf\n", (long double)v);`等、明示的なキャストを行い正しく引数を与える必要があります。

別の根本的な解決方法としてはgccのコンパイルオプション`-mlong-
double-64`を使うことです。しかし、これを実現するには`printf()`を含むランタイムすべてをビルドし直す必要があります。というかgccからビルドし直す方が早いでしょうか。

# `__USE_MINGW_ANSI_STDIO`マクロについて

`<_mingw.h>`には次のコメントがあります。

[code]

    /* These are defined by the user (or the compiler)
       to specify how identifiers are imported from a DLL.
    
       __DECLSPEC_SUPPORTED            Defined if dllimport attribute is supported.
       __MINGW_IMPORT                  The attribute definition to specify imported
                                       variables/functions.
       _CRTIMP                         As above.  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 deviate from standard MSVC
       compatible behaviour; these may be specified directly in user code,
       activated implicitly, (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.
    
[/code]

_These are defined by the user_ とか _these may be specified directly in user
code_ とありますから、指定しても構わないと思います。  
そもそも、MinGW stdioとMSVCRT
stdioとの切り替え選択という内部実装に強く依存した行為ですから、指定するには細心の注意を払うべきですし、注意して行うのであればためらう必要はないかと。

> たとえば、先の‌​メールで指摘されているように`_POSIX‌​_C_SOURCE`、`_XOPEN_SOURCE`
> (または`_GNU_SOURCE`) を使うとどうなるのでしょうか？

これに関しては本回答及びコードをよく確認してください。`#if
__USE_MINGW_ANSI_STDIO`を不成立させたいのですから、いずれを`#define`しても期待する結果は得られません。

