[cppx] Is C4099 really a sillywarning? (MSVC sillywarnings)

Evidently some people get to my blog by googling up C4099, the MSVC warning that you’ve used struct in one place and class in another place, for the same class. This is one of the alleged sillywarnings in my sillywarnings suppression header. But given e.g. the discussion at StackOverflow, is it really a sillywarning, or perhaps something to take seriously?

The warning

Here’s an example generating warning C4099 with MSVC:

File [basic/C4099.cpp], complete:

struct Ouch;

class Ouch {};

int main() {}

Compiling with MSVC 7.1 (the same is obtained with MSVC 9.0), via a batch file [msvc.bat] that invokes the MSVC compiler cl with appropriate options to make it more standard-compliant:

W:\basic> msvc C4099.cpp
C4099.cpp
C4099.cpp(3) : warning C4099: 'Ouch' : type name first seen using 'struct' now seen using 'class'
        C4099.cpp(1) : see declaration of 'Ouch'

W:\basic> _

What does warning C4099 mean?

In standard C++ a forward declaration using struct is equivalent to one using class, there is no difference. When you define a class there is a difference in the default accessibility of base classes and members, but there’s no difference for a forward declaration. The C++98 standard is perhaps not overly clear on this, but it’s clear enough, in §7.1.5.3/3 (emphasis added):

The class-key or enum keyword present in the elaborated-type-specifier shall agree in kind with the declaration to which the name in the elaborated-type-specifier refers. This rule applies to the form of elaborated-type-specifier that declares a class-name or friend class since it can be construed as referring to the definition of the class. Thus, in any elaborated-type-specifier, the enum keyword shall be used to refer to an enumeration (7.2), the union class-key shall be used to refer to a union (clause 9), and either the class or struct class-key shall be used to refer to a class (clause 9) declared using the class or struct class-key.

Thus, the warning does not tell you that there’s anything wrong with your code.

On the contrary, as I discuss below, if this warning occurs then a potential problem is probably not present in your code! :-) It is IMO a true sillywarning. But this sillywarning is vaguely associated with the fact that MSVC is non-standard in that it does not honor the equivalence of struct and class for a forward-declaration.

MSVC name decoration – a little buggy…

With MSVC compiling [foo.cpp] generates an object code file [foo.obj] where each source code name has been extended with some extra text that specifies the type. This is called name mangling or name decoration. In practice all C++ compilers use name mangling to implement cross-module type checking and to support C++ function overloading with old C-oriented linkers, but it is unfortunately not standardized, and Microsoft does not fully document the MSVC name mangling.

You can inspect the decorated names using e.g. the MSVC dumpbin tool. dumpbin “unmangles” the name for you, and you can also use the MSVC undname tool to do that separately. And if you want to do it programmatically then there are a number of APIs for that, but let’s just check what mangled names look like & what they encode:

File [mangled/mangled_names.cpp], complete:

struct  CompleteStruct {};
class   CompleteClass {};

void foo( CompleteStruct& ) {}
void foo( CompleteClass& ) {}

struct  ForwardStruct;
class   ForwardClass;

void foo( ForwardStruct& ) {}
void foo( ForwardClass& ) {}

Compiling & checking the object code names:

W:\mangled> msvc /c mangled_names.cpp
mangled_names.cpp

W:\mangled> dumpbin /symbols mangled_names.obj | find "foo("
00A 00000000 SECT3  notype ()    External     | ?foo@@YAXAAUCompleteStruct@@@Z (void __cdecl foo(struct CompleteStruct &))
00B 00000010 SECT3  notype ()    External     | ?foo@@YAXAAVCompleteClass@@@Z (void __cdecl foo(class CompleteClass &))
00C 00000020 SECT3  notype ()    External     | ?foo@@YAXAAUForwardStruct@@@Z (void __cdecl foo(struct ForwardStruct &))

00D 00000030 SECT3  notype ()    External     | ?foo@@YAXAAVForwardClass@@@Z (void __cdecl foo(class ForwardClass &))

W:\mangled> _

To the right dumpbin presents first each mangled name, and then an unmangled version (what you’d get from undname). And in the unmangled names you can see that the source code’s use of struct and class is encoded in each mangled name. This is OK for CompleteStruct and CompleteClass, where it helps with enforcing the standard’s One Definition Rule (which requires identical token sequences in two definitions of the same class), but it’s decidedly not OK for ForwardStruct and ForwardClass, where nothing is known about the definitions’ use of struct versus class.

To do this properly the decorated name for a function involving a forward-declared class with unknown definition, would have to use some third code for struct-or-class, which the linker would then have to resolve like a kind of wildcard. An easier way could be to punt on the checking and not encode the use of struct versus class, at all. That’s perhaps not as drastic as it sounds.

No problem = warning.

Given the MSVC name mangling scheme, perfectly fine, standard C++ code can fail to link. In this case you will generally not get warning C4099, because the problem is due to some declaration using only a forward declaration of a class, with the definition unknown at that point, and thus no detection of inconsistent use of struct and class. Making the definition known is one way to work around this MSVC bug; the code then links fine, but may then produce warning C4099.

I.e., when there is a problem then you do not get the warning, but when everything’s fine you may get it.

Consider:

File [fooblah/Blah.h], complete:

#ifndef BLAH_H
#define BLAH_H

class Blah
{
private:
    int     x_;
public:
    Blah( int x ): x_( x ) {}
    int x() const { return x_; }
};

#endif

File [fooblah/foo.h], complete:

#ifndef FOO_H
#define FOO_H

struct Blah;

Blah foo();

#endif

File [fooblah/foo.cpp], complete:

#include "Blah.h"

Blah foo() { return Blah( 42 ); }

Here, perhaps to avoid excessive build times, the programmer has just forward-declared Blah in file [foo.h], but using struct instead of class, which is perfectly fine standard C++ but not so good with MSVC:

File [fooblah/main_e.cpp], complete:

// Yields no warning, but fails to LINK with MSVC.
#include "foo.h"

void someFuncTakingACallback( Blah (*)() ) {}

int main()
{
    someFuncTakingACallback( &foo );
}

Compiling:

W:\fooblah> msvc main_e.cpp foo.cpp
main_e.cpp
foo.cpp
Generating Code...
main_e.obj : error LNK2019: unresolved external symbol "struct Blah __cdecl foo(void)" (?foo@@YA?AUBlah@@XZ) referenced in function _main
main_e.exe : fatal error LNK1120: 1 unresolved externals

W:\fooblah> _

Well, well, well, let’s just give it the definition of Blah, then:

File [fooblah/main_w.cpp], complete:

// Links fine but yields WARNING C4099 with MSVC.
#include "foo.h"
#include "Blah.h"       // Needs def. of Blah to link properly.

void someFuncTakingACallback( Blah (*)() ) {}

int main()
{
    someFuncTakingACallback( &foo );
}

Compiling:

W:\fooblah> msvc main_w.cpp foo.cpp
main_w.cpp
w:\fooblah\Blah.h(5) : warning C4099: 'Blah' : type name first seen using 'struct' now seen using 'class'
        w:\fooblah\foo.h(4) : see declaration of 'Blah'
foo.cpp
Generating Code...

W:\fooblah> _

So, this is where you get warning C4099: when your code is OK, even for MSVC.

Conclusion

Warning C4099 does tell you something, namely that it might be possible to change the code so that it is still standard-conforming but won’t link with MSVC. But then, if you get this warning it may just as likely stem from some header that you have no control over, like a Microsoft header. And if there is an actual problem (for MSVC) then you get a link error and generally no warning.

When I included C4099 in my sillywarnings suppression header it was not because I had ever had this warning generated for my own code. Instead, it had been generated by code that I had no control over. And thus at least for my own coding it was and is just an undesirable sillywarning.

However, that is for a style of programming based on 100% clean builds. In a setting where a build regularly produces a stream of warnings that one ignores, warning C4099 probably won’t distract one from more real warnings. But in such a setting it probably won’t help either (to the degree that it could), since in such a setting warnings are regularly ignored.

Cheers, – Alf

About these ads

One comment on “[cppx] Is C4099 really a sillywarning? (MSVC sillywarnings)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s