Menu

#2251 Including Windows.h makes compiler to ignore cdecl attribute of a function.

WSL
closed
Bug
works-for-me
Unknown
False
2015-07-15
2015-02-09
Marcos Diaz
No

Hi,
In Windows 7,

gcc:
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=C:/Program\ Files\ (x86)/mingw-w64/i686-4.8.2-win32-dwarf-rt
_v3-rev4/mingw32/bin/../libexec/gcc/i686-w64-mingw32/4.8.2/lto-wrapper.exe
Target: i686-w64-mingw32
Configured with: ../../../src/gcc-4.8.2/configure --host=i686-w64-mingw32 --buil
d=i686-w64-mingw32 --target=i686-w64-mingw32 --prefix=/mingw32 --with-sysroot=/c
/mingw482/i686-482-win32-dwarf-rt_v3-rev4/mingw32 --with-gxx-include-dir=/mingw3
2/i686-w64-mingw32/include/c++ --enable-shared --enable-static --disable-multili
b --enable-languages=ada,c,c++,fortran,objc,obj-c++,lto --enable-libstdcxx-time=
yes --enable-threads=win32 --enable-libgomp --enable-libatomic --enable-lto --en
able-graphite --enable-checking=release --enable-fully-dynamic-string --enable-v
ersion-specific-runtime-libs --disable-sjlj-exceptions --with-dwarf2 --disable-i
sl-version-check --disable-cloog-version-check --disable-libstdcxx-pch --disable
-libstdcxx-debug --enable-bootstrap --disable-rpath --disable-win32-registry --d
isable-nls --disable-werror --disable-symvers --with-gnu-as --with-gnu-ld --with
-arch=i686 --with-tune=generic --with-libiconv --with-system-zlib --with-gmp=/c/
mingw482/prerequisites/i686-w64-mingw32-static --with-mpfr=/c/mingw482/prerequis
ites/i686-w64-mingw32-static --with-mpc=/c/mingw482/prerequisites/i686-w64-mingw
32-static --with-isl=/c/mingw482/prerequisites/i686-w64-mingw32-static --with-cl
oog=/c/mingw482/prerequisites/i686-w64-mingw32-static --enable-cloog-backend=isl
 --with-pkgversion='i686-win32-dwarf-rev4, Built by MinGW-W64 project' --with-bu
gurl=http://sourceforge.net/projects/mingw-w64 CFLAGS='-O2 -pipe -I/c/mingw482/i
686-482-win32-dwarf-rt_v3-rev4/mingw32/opt/include -I/c/mingw482/prerequisites/i
686-zlib-static/include -I/c/mingw482/prerequisites/i686-w64-mingw32-static/incl
ude' CXXFLAGS='-O2 -pipe -I/c/mingw482/i686-482-win32-dwarf-rt_v3-rev4/mingw32/o
pt/include -I/c/mingw482/prerequisites/i686-zlib-static/include -I/c/mingw482/pr
erequisites/i686-w64-mingw32-static/include' CPPFLAGS= LDFLAGS='-pipe -L/c/mingw
482/i686-482-win32-dwarf-rt_v3-rev4/mingw32/opt/lib -L/c/mingw482/prerequisites/
i686-zlib-static/lib -L/c/mingw482/prerequisites/i686-w64-mingw32-static/lib -Wl
,--large-address-aware'
Thread model: win32
gcc version 4.8.2 (i686-win32-dwarf-rev4, Built by MinGW-W64 project)

GNU ld (GNU Binutils) 2.24

mingw32.

Having this example code:

#include <Windows.h>

class Test
{
    __attribute__((cdecl)) void functioncdecl();
    void functionno();
    int i;
};

void Test::functioncdecl()
{
    i++;
}

void Test::functionno()
{
    i++;
};

compiling with:

g++ -S pruebacdecl.cpp

I can see the assembler generated for functioncdecl, that is not following the cdecl attribute:

__ZN4Test13functioncdeclEv:
LFB473:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $4, %esp
    movl    %ecx, -4(%ebp)
    movl    -4(%ebp), %eax
    movl    (%eax), %eax
    leal    1(%eax), %edx
    movl    -4(%ebp), %eax
    movl    %edx, (%eax)
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE473:
    .align 2
    .globl  __ZN4Test10functionnoEv
    .def    __ZN4Test10functionnoEv;    .scl    2;  .type   32; .endef
__ZN4Test10functionnoEv:
LFB474:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $4, %esp
    movl    %ecx, -4(%ebp)
    movl    -4(%ebp), %eax
    movl    (%eax), %eax
    leal    1(%eax), %edx
    movl    -4(%ebp), %eax
    movl    %edx, (%eax)
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret

As we can see both functions remains the same,
But, if I comment the #include <windows.h> line, The output assembly is correct:</windows.h>

__ZN4Test13functioncdeclEv:
LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    movl    8(%ebp), %eax
    movl    (%eax), %eax
    leal    1(%eax), %edx
    movl    8(%ebp), %eax
    movl    %edx, (%eax)
    popl    %ebp
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc

(Note that now follows the cdecl attribute)

So, it is ignoring the cdecl attribute, just by including Windows.h, and no warning is emitted.

Discussion

  • Keith Marshall

    Keith Marshall - 2015-07-15
    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -1,5 +1,7 @@
     Hi,
     In Windows 7,
    +
    +~~~~
     gcc:
     Using built-in specs.
     COLLECT_GCC=g++
    @@ -38,10 +40,11 @@
     GNU ld (GNU Binutils) 2.24
    
     mingw32.
    -
    +~~~~
    
     Having this example code:
    
    +~~~~
     #include <Windows.h>
    
     class Test
    @@ -60,12 +63,17 @@
     {
        i++;
     };
    +~~~~
    
     compiling with:
    +
    +~~~~
     g++ -S pruebacdecl.cpp
    +~~~~
    
     I can see the assembler generated for functioncdecl, that is not following the cdecl attribute:
    
    +~~~~
     __ZN4Test13functioncdeclEv:
     LFB473:
        .cfi_startproc
    @@ -109,10 +117,12 @@
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
    +~~~~
    
     As we can see both functions remains the same,
     But, if I comment the #include <Windows.h> line, The output assembly is correct:
    
    +~~~~
     __ZN4Test13functioncdeclEv:
     LFB0:
        .cfi_startproc
    @@ -131,6 +141,7 @@
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    +~~~~
    
     (Note that now follows the cdecl attribute)
    
    • status: unread --> assigned
    • assigned_to: Keith Marshall
    • Group: OTHER --> WSL
     
  • Keith Marshall

    Keith Marshall - 2015-07-15

    Sigh ... I do wish posters would use appropriate mark-up, when filing bug reports, (and use the "preview" facility, to see what a mess they are creating, otherwise).

    gcc version 4.8.2 (i686-win32-dwarf-rev4, Built by MinGW-W64 project)

    Well, we don't support the foul-ups created by that renegade project. However, since this seems to be related to [#2256], I guess this could stand further scrutiny.

    it is ignoring the cdecl attribute, just by including Windows.h ...

    So, maybe an issue in <windows.h>, which may be common to both projects. Curiously, for both GCC-3.4.5, and GCC-4.8.2, I see:

    $ use mingw32-gcc-3.4.5
    $ mingw32-gcc --version
    mingw32-gcc (GCC) 3.4.5 (mingw-vista special r2)
    ...
    $ mingw32-gcc -E -dM -xc /dev/null | grep cdecl
    #define _cdecl __attribute__((__cdecl__))
    #define __cdecl __attribute__((__cdecl__))
    
    $ use mingw32-gcc-4.8.2
    $ mingw32-gcc --version
    mingw32-gcc (GCC) 4.8.2
    ...
    $ mingw32-gcc -E -dM -xc /dev/null | grep cdecl
    #define _cdecl __attribute__((__cdecl__))
    #define __cdecl __attribute__((__cdecl__))
    

    so clearly, both versions of the compiler provide built-in, (and entirely correct and consistent), definitions for both _cdecl and __cdecl; it would seem that <windows.h> may be interfering with the _cdecl variant.

     

    Related

    Issues: #2256

  • Keith Marshall

    Keith Marshall - 2015-07-15

    Sorry, but I cannot reproduce this ... neither with my own self-built mingw32-g++ Linux hosted cross-compiler, nor with MinGW.org's g++-4.7.2 release running on Win7 Home Premium in VirtualBox. (In both cases, the MinGW headers are the current mingwrt-3.21.1 and w32api-3.17).

    Since you didn't provide a downloadable copy, and I've had trouble simply copying and pasting from the browser in the past, I chose to retype the following adaptation of your test case:

    $ cat foo.cpp
    #include <windows.h>
    
    class foo
    {
      __attribute__((cdecl)) void cdecl_increment();
      void undecorated_increment();
      int i;
    };
    
    void foo::cdecl_increment(){ ++i; };
    void foo::undecorated_increment(){ ++i; };
    

    Compiling with:

    $ cat foo.cpp | mingw32-g++ -S -o foo-with-windows-h.s -xc++ -
    $ sed 1,2d foo.cpp | mingw32-g++ -S -o foo-without-windows-h.s -xc++ -
    

    and subsequently comparing the two generated assembly code variants shows no effective difference whatsoever:

    $ diff -u foo*.s
    --- foo-without-windows-h.s 2015-07-15 12:46:16.498134556 +0100
    +++ foo-with-windows-h.s    2015-07-15 12:45:17.753779202 +0100
    @@ -4,7 +4,7 @@
        .globl  __ZN3foo15cdecl_incrementEv
        .def    __ZN3foo15cdecl_incrementEv;    .scl    2;  .type   32; .endef
     __ZN3foo15cdecl_incrementEv:
    -LFB0:
    +LFB8:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
    @@ -21,12 +21,12 @@
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    -LFE0:
    +LFE8:
        .align 2
        .globl  __ZN3foo21undecorated_incrementEv
        .def    __ZN3foo21undecorated_incrementEv;  .scl    2;  .type   32; .endef
     __ZN3foo21undecorated_incrementEv:
    -LFB1:
    +LFB9:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
    @@ -45,5 +45,5 @@
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    -LFE1:
    +LFE9:
        .ident  "GCC: (GNU) 4.8.2"
    

    (sure, some local labels differ, but that isn't an effective difference).

    The generated assembly code, effectively the same in both cases, is:

    $ cat foo-with-windows-h.s 
        .file   ""
        .text
        .align 2
        .globl  __ZN3foo15cdecl_incrementEv
        .def    __ZN3foo15cdecl_incrementEv;    .scl    2;  .type   32; .endef
    __ZN3foo15cdecl_incrementEv:
    LFB8:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        movl    8(%ebp), %eax
        movl    (%eax), %eax
        leal    1(%eax), %edx
        movl    8(%ebp), %eax
        movl    %edx, (%eax)
        popl    %ebp
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    LFE8:
        .align 2
        .globl  __ZN3foo21undecorated_incrementEv
        .def    __ZN3foo21undecorated_incrementEv;  .scl    2;  .type   32; .endef
    __ZN3foo21undecorated_incrementEv:
    LFB9:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $4, %esp
        movl    %ecx, -4(%ebp)
        movl    -4(%ebp), %eax
        movl    (%eax), %eax
        leal    1(%eax), %edx
        movl    -4(%ebp), %eax
        movl    %edx, (%eax)
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    LFE9:
        .ident  "GCC: (GNU) 4.8.2"
    

    which seems to match what you say to be correct for cdecl, in both cases.

    FWIW, I'm by no means a C++ expert ... I don't know what effect cdecl should be expected to produce here. I do know the distinction between cdecl and stdcall insofar as the latter requires the called function to pop the argument frame from the stack on return, whereas the former leaves that responsibility with the caller, but this example doesn't exercise that feature anyway, (since there are no arguments passed to either function). Why should cdecl be required to produce different code, beyond satisfying this distinction, for functions which are otherwise identical?

     
  • Marcos Diaz

    Marcos Diaz - 2015-07-15

    Hi, the difference I was looking is that for cdecl the 'this' pointer is passed in the stack:

    movl    8(%ebp), %eax
    

    and the stdcall pass 'this' pointer through the ecx register:

    movl    %ecx, -4(%ebp)
    

    Sorry for the mark-up. I will try to reproduce this, and if i don't see this here i will try to submit this to MinGW-W64 project(sorry for this also, i didn't know they were separate projects)
    Greetings.

     
    • Keith Marshall

      Keith Marshall - 2015-07-15

      Hi, the difference I was looking is that for cdecl the 'this' pointer is passed in the stack:

      Yes, I'd kind of figured that out, mostly by examining the (simpler) code generated when the -O2 compiler option is also given:

      $ mingw-g++ -S -O2 foo.cpp
      
      $ cat foo.s
      ...
      __ZN3foo15cdecl_incrementEv:
      LFB19:
          .cfi_startproc
          movl    4(%esp), %eax
          addl    $1, (%eax)
          ret
          .cfi_endproc
      LFE19:
      ...
      

      vs. the corresponding code, with __attribute__((fastcall)) substituted in place of __attribute__((cdecl)):

      ...
      @_ZN3foo18fastcall_incrementEv@4:
      LFB20:
          .cfi_startproc
          addl    $1, (%ecx)
          ret
          .cfi_endproc
      LFE20:
      ...
      

      and the stdcall pass 'this' pointer through the ecx register:

      No, stdcall would also pass the this pointer on the stack; what we're seeing here is a compiler default choice which is equivalent to the fastcall protocol:

      ...
      __ZN3foo21undecorated_incrementEv:
      LFB21:
          .cfi_startproc
          addl    $1, (%ecx)
          ret
          .cfi_endproc
      LFE21:
      ...
      

      i will try to submit this to MinGW-W64 project(sorry for this also, i didn't know they were separate projects)

      Many people are confused by this. Blame the mingw-w64 renegades, who persistently infringe the registered trade mark of MinGW.org, and who fail to make it clear that they are, in no way, associated with us.

      Since I am unable to reproduce your issue, with any compiler which MinGW.org supports, I am closing this ticket. I'm not prepared to support a rogue fork from our code base.

       
  • Keith Marshall

    Keith Marshall - 2015-07-15
    • status: assigned --> closed
    • Resolution: none --> works-for-me
     
  • Marcos Diaz

    Marcos Diaz - 2015-07-15

    Thanks, I already tried the same in a recent mingw I downloaded and couldn't see the problem.

     
  • Keith Marshall

    Keith Marshall - 2015-07-15

    Just to complete the picture:

    and the stdcall pass 'this' pointer through the ecx register:

    No, stdcall would also pass the this pointer on the stack ...

    Here's the compiler output I see, compiling with -O2, and substituting __attribute__((stdcall)) in place of __attribute__((cdecl)):

    ...
    __ZN3foo17stdcall_incrementEv@4:
    LFB22:
        .cfi_startproc
        movl    4(%esp), %eax
        addl    $1, (%eax)
        ret $4
        .cfi_endproc
    LFE22:
    

    Notice that, just like the cdecl variant, the this pointer is passed on the stack. The differences are that the function name is decorated by an additional @4 suffix, indicating that the caller will provide a four byte stack frame to pass the this pointer, and that the ret $4 does indeed pop these four bytes from the stack, in addition to the return address, when this function returns, as is required by the stdcall protocol.

     
Monday.com Logo