Furkanzmc Furkanzmc - 3 months ago 17
C++ Question

LNK2001 on VS 2013 (MSVC 18) but not on VS 2015 (MSVC 19)

I was using an earlier version of Cocos2dx to write a game and compiling it with VS 2013. Note that I'm using CMake and Qt Creator with both compiler versions. When Cocos2dx v3.12 came out, I decided to upgrade the lib to that version in my game and started using VS 2015. Then I started getting this error:


QCardManager.cpp.obj:-1: error: LNK2001: unresolved external symbol
"public: static class QCard * __cdecl QCard::create(enum PLAYER,struct
Question const *,enum CARD_TYPE,int const &)"
(?create@QCard@@SAPAV1@W4PLAYER@@PBUQuestion@@W4CARD_TYPE@@ABH@Z)


And I did not get that error when I was using VS 2013. After a couple of hours of debugging I found out the reason.

Here's the rough decleration of
QCard
:

#include "2d/CCSprite.h"
#include "CommonVariables.h"

class RandomPostureSprite;
class Question;
namespace cocos2d
{
class Label;
}

enum class CARD_TYPE {
QUESTION,
OPTION
};

class QCard : public cocos2d::Sprite
{
public:
static QCard *create(PLAYER player, const Question *question, CARD_TYPE type, const int &index);
}


And I had the proper implementation of that function in
QCard.cpp
file and that file was also properly added to the project.
So the problem was the
class Question;
forward declaration. I included the
QuestionParser.h
file in
QCard.cpp
but since I used a forward declaration for
QCard
in
QCard.h
,
QCardManager.cpp
file did not have the implementation for
Question
and hence the linker error.

Here's my question: I realize that what VS 2015 does should be the expected behaviour. But why is that behaviour happening? The same code compiles with no error on VS 2013 but not on VS 2015. I read the Breaking Changes in Visual C++ 2015 guide and couldn't not see anything that was related.

EDIT 1:
Turns out the forward declaration should have been
struct Question
instead of
class Question
. When I try to use
QCard::create
in
QCardManager.cpp
I get the aforementioned linker error. But not in
TimerHUD.cpp
, which is in the same directory. I'll post the summary contents of them both. Keep in mind that I'm keeping the declaration of
QCard
the same with this edit.

The Question struct, which is in
QuestionParser.h
:

struct Question {
Question()
: type()
, source()
, alias()
, color(0, 0, 0)
{}
};


QCardManager.h

// Cocos2dx
#include "math/Vec2.h"
#include "math/CCGeometry.h"
// Utilities
#include "CommonVariables.h"
// Local
#include "GameDefinitions.h"
#include "QuestionParser.h"// This has the Question struct

// Forward declerations
class QCard;
namespace cocos2d
{
class Layer;
class Sprite;
}

class QCardManager
{
}


QCardManager.cpp

#include "QCardManager.h"
// Local
#include "QCard.h"
#include "RandomPostureSprite.h"
// Utilities
#include "GameManager.h"
#include "GameSettings.h"
#include "CocosUtils.h"
// Cocos2dx
#include "cocos2d.h"
using namespace cocos2d;

QCardManager::QCardManager(PLAYER player, Layer &parent)
{
// This line gives the linker error
QCard::create(PLAYER::PLAYER_ONE, nullptr, CARD_TYPE::QUESTION, 1);
}


QCardManager
raises the linker error. But
TimerHUD
does not. I'm sharing the contents now.

TimerHUD.h

// Cocos2dx
#include "2d/CCNode.h"

namespace cocos2d
{
class Sprite;
class Label;
}

class TimerHUD : public cocos2d::Node
{
}


TimerHUD.cpp

// Cocos2dx
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
// Local
#include "GameDefinitions.h"
// Utilities
#include "GameManager.h"
#include "GameSettings.h"
#include "CocosUtils.h"
#include "QCard.h"
using namespace cocos2d;

TimerHUD::TimerHUD()
{
// This does not raise the linker error
QCard::create(PLAYER::PLAYER_ONE, nullptr, CARD_TYPE::QUESTION, 1);
}

Answer

You shouldn't need Question's definition for this to link properly. The U in @PBUQuestion@ and the linker error seem to talk about struct Question instead of class Question, so you have a mismatch between the declaration and definition of Question. If you were to raise your warning level, you'd see that:

> type a.cpp
struct A;
class A {};

> cl /Wall a.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24213.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

a.cpp
a.cpp(2): warning C4099: 'A': type name first seen using 'struct' now seen using 'class'
a.cpp(2): note: see declaration of 'A'

But because your warning level is too low, you don't get that diagnostic.

Your code seems to be valid from the standard's point of view. From C++11 9.1/2:

A declaration consisting solely of class-key identifier; is either a redeclaration of the name in the current scope or a forward declaration of the identifier as a class name. It introduces the class name into the current scope.

However, Visual C++ mangles the name of a function differently depending on whether a name is a class or a struct:

> undname ?f@@YAXPAUA@@@Z
void __cdecl f(struct A *)

> undname ?f@@YAXPAVA@@@Z
void __cdecl f(class A *)

Note that struct is mangled as U and class as V. This is definitely a bug in Visual C++, which shouldn't treat class and struct differently.

Once this is understood, your error is easy to reproduce:

> type a.cpp
class A;          // declares A as a class
void f(A* a);     // declares f(class A*)

int main()
{
    f(nullptr);   // calls f(class A*)
}


> type b.cpp
struct A {};      // defines A as a struct, mismatch!
void f(A* a) {}   // defines f(struct A*)!


> cl /nologo a.cpp b.cpp
a.cpp
b.cpp
Generating Code...
a.obj : error LNK2019: unresolved external symbol
    "void __cdecl f(class A *)" (?f@@YAXPAVA@@@Z) referenced
        in function _main
a.exe : fatal error LNK1120: 1 unresolved externals

So the reason you're having these weird issues is because the behaviour depends on whether a particular translation unit (basically, a .cpp) sees Question as a class or a struct. This in turn depends on which headers are included.

As for the reason why this wasn't a problem in Visual C++ 2013, I doubt that the mangling scheme changed recently. It sounds like this bug has been present since at least 6.0. You may have changed the order of includes inadvertently.

Comments