

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


CC BY-SA 4.0
2017-04-14T12:12:52.837
34013
2022-11-06T07:23:27.633
2022-11-06T07:23:27.633
4236
5044
question
c windows gcc mingw c99
TDM-GCC 5.1.0 で c99 オプションを指定したコンパイル時、printf の %lf で出力される結果が想定と異なる


[@nekketsuuu
さんのコメント](https://ja.stackoverflow.com/questions/34013/printf-%E3%81%AE-
lf-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/34023?noredirect=1#comment33587_34013)の[リンク先の回答](https://stackoverflow.com/questions/27363795/mingw-
doesnt-produce-warnings)で指摘されているように、どうやらTDM での元になっているMinGW4.8からのバグ(`%lf`が`long
double`への指定だと解釈される)のようです。

[code]

    printf("%lf\n", (long double)v);
    
[/code]

とすると期待する値が表示されました。（つまり`long double`への指定だと解釈されていた）  
(本来`long double`を意味する`%Lf`は期待通り動作します）

別の場所で`__USE_MINGW_ANSI_STDIO` マクロを指定するというような情報もあったのですが、  
少なくとも現状は機能しないようです。  
([sayuriさんの回答](https://ja.stackoverflow.com/a/34023/19110)から、機能しないというより`-std=c99`の場合既に指定された状態）

MinGW-w64ではこのような問題はないらしいのでコンパイラを切り替えるか

`-std=c99` の代わりに `-std=gnu99`を使用するのが簡易な解決のようです。  
（しかし、その場合`%zu`が使用できなくなる）

`-std=c99` のような(`-std=c11` でも）標準規格指定がされた時に参照ヘッダが切り替わることが動作が異なる理由らしいですが、  
そこら辺の切り替えをユーザーから問題を起こすことなしにうまく切り替えることができたらいいんですが、多分色々絡み合ってて難しいんでしょうね。

* * *

  * 関連しそうなリンク (コメント参照) 
    * mingw.orgにあるwiki上の[C99についてのページ](http://www.mingw.org/wiki/c99)。ここにMSVCRTとMinGWのC99についての歴‌​史が短く書いてあります。
    * MinGWのバグトラッカーにあった「`%lf`の表示がおかしい」というissueに対して、「ISO-C99では`printf(‌​)`の "l" は単に無視されるので無駄だ。また、Microsoftは`%lf`は`%Lf` (double long) になると言っている。`%f`を使おう」というコメントがついているところ: <https://sourceforge.net/p/mingw/bugs/2253/>
    * Microsoftでの[サイズ指定解釈](https://msdn.microsoft.com/ja-jp/library/tcxf1dw6.aspx)
    * `__USE_MINGW_ANSI_STDIO`を使ったら？と言っている[Stack Overflowでの回答](https://stackoverflow.com/questions/26296058/cant-print-correctly-a-long-double-in-c)


CC BY-SA 3.0
2017-04-14T13:48:39.190
34015
2017-04-15T15:50:16.237
2017-05-23T12:38:55.307
-1
5044
34013
answer


`-std=c99`指定の時は、`"%Lf"`と同じ扱いをされてしまっているのかな？  
`printf()`は可変長引数で`float`は`double`に拡張されるため、昔は`"%lf"`は間違いでした。  
[C99で`"%lf"`が許されるようになった](http://seclan.dll.jp/c99d/c99d0a2.htm#appendix_printf)とはいえ、敢えて`"%lf"`にしなくても良いのではないでしょうか。

[code]

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

コンパイル結果：

[code]

    d:\tmp>gcc -Wall -pedantic -std=c99 double.c
    
[/code]

実行結果：

[code]

    d:\tmp>a.exe
    sizeof(double)      = 8
    sizeof(long double) = 12
    %f  - double       :3.141593
    %lf - double       :0.000000
    %Lf - double       :0.000000
    %f  - (long double):-88793646155857547477275137080339417618448384.000000
    %lf - (long double):3.141593
    %Lf - (long double):3.141593
    
[/code]

コンパイル結果：

[code]

    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' expects argument of type 'double', but argument 2 has type 'long double' [-Wformat=]
         printf("%%f  - (long double):%f\n", (long double)v);
                ^
    double.c:12:12: warning: format '%lf' expects 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]
    
[/code]

実行結果：

[code]

    d:\tmp>a.exe
    sizeof(double)      = 8
    sizeof(long double) = 12
    %f  - double       :3.141593
    %lf - double       :3.141593
    %Lf - double       :3.141593
    %f  - (long double):-88793646155857547000000000000000000000000000.000000
    %lf - (long double):-88793646155857547000000000000000000000000000.000000
    %Lf - (long double):-88793646155857547000000000000000000000000000.000000
    
[/code]

参考：

  * [プログラミング言語 C の新機能 - A.2.5 長さ修飾子](http://seclan.dll.jp/c99d/c99d0a2.htm#appendix_printf "プログラミング言語 C の新機能")


CC BY-SA 3.0
2017-04-14T14:32:38.333
34021
2017-04-15T05:46:09.870
2017-04-15T05:46:09.870
7291
7291
34013
answer


# `-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`しても期待する結果は得られません。


CC BY-SA 3.0
2017-04-14T23:47:56.663
34023
2017-04-17T01:24:22.470
2017-04-17T01:24:22.470
4236
4236
34013
answer


[質問者さんのコメントの記述](https://ja.stackoverflow.com/questions/34013/printf-%E3%81%AE-
lf-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/34028#comment33610_34013)より

> （TDM-GCCを）Ｃ９９での動作をさせたい

とのことですが、[snprintf
の返値について](https://ja.stackoverflow.com/q/31646/4236)でもコメントしましたが、規格・仕様と実装は別問題です。GCCはコンパイラーの責任範囲にてC99に準拠しているのかもしれませんが、TDM-
GCCが実行環境に利用しているMinGWやVisual C++ 6.0はC99準拠を謳っていないはずです。総じてTDM-GCCはC99に準拠していません。

C99を使用したいのであれば、C99に準拠した環境を選択すべきです。


CC BY-SA 3.0
2017-04-15T03:13:07.830
34028
2017-04-15T04:04:53.170
2017-04-15T04:04:53.170
4236
4236
34013
answer