Strange problem with static const member variable

Does anyone understand what the problem with the following code is? It compiles with Visual C++ 2012 but does not with g++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//a.h

#ifndef Loaded
#define Loaded

using namespace std;

class MyClass{
	public:
		static const int MyStaticValue = 200;
		int MyValue;

		MyClass& operator=(const int& Op);
};

#endif

//a.cpp

#include "a.h"

using namespace std;

MyClass& MyClass::operator=(const int& Op){
	MyValue = Op;
	return(*this);
}

//b.cpp

#include <iostream>
#include "a.h"

using namespace std;

int main(){
	MyClass A;
	
	A = MyClass::MyStaticValue;
	
	cout << MyClass::MyStaticValue << endl;
	cout << A.MyValue << endl;

	return(0);
}


If I try to compile this using the command

g++ a.cpp b.cpp

I get an "undefined reference to 'MyClass::MyStaticValue'" error for the line "A = MyClass::MyStaticValue;" in main(). The strange thing is that if I change the line to "A = (int) MyClass::MyStaticValue;" it works fine and the output is
200
200
as expected. Does anyone understand the problem?

Edit: The code also compiles under g++ if I move the defintion of MyStaticValue from a.h to a.cpp by const int MyClass::MyStaticValue = 200;
Last edited on
The problem also goes away if you change the signature of your operator= to

MyClass& operator=(int Op);

The issue is that you are using MyStaticValue in a way that requires the compiler to know the address of the static member, rather than just its value. As inline declaration only works for the value; it does not result in an addressable member.

In this case, it's because your operator= takes a reference to an integer.

Adding

const int MyClass::MyStaticValue; // no initialization, as in header

to a.cpp will fix the problem. Or you can move the initialization (= 200) from the header to the cpp file. But it can only be in one of the two places.

Andy

PS You do know it's not normal practice to pass built-in types like int, double by const ref? Normally they're passed by value (in params) or non-const refs (out params).
Last edited on
Thank you for your reply! Makes perfekt sense, though I still don't understand why Visual C++ 2012 compiles the code. Seems that the compiler recognizes what I intend and does the right thing. Another thing I don't understand ist why something like A = 15; works if I pass on the integer by reference. If that static const variable does not have an address, why does a literal? Shouldn't it be both or none?
If that static const variable does not have an address, why does a literal? Shouldn't it be both or none?

It is both. When required, the compiler will create a temporary variable and then use it to make a call.

If you look at the disassembly of the (unoptimized) exe code for this tiny program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <iomanip>
using namespace std;

#define PASS_BY_REF

#ifdef PASS_BY_REF
void write_int(const int& value) {
	cout << "by ref = " << value << endl;
}
#else
void write_int(int value) {
	cout << "by value = " << value << endl;
}
#endif

int main() {
	cout << hex << showbase;
	write_int(0x1234); // in hex so easier to spot in disassembly
	return 0;
}


Notes

add dest, src : This adds src to dest.
call proc : pushes the address of the next opcode onto the top of the
stack, and jumps to the specified location.
lea dest, src : (Load Effective Address) calculates the address of the src
operand and loads it into the dest operand.
dest, src : (Move) copies the src operand into the dest operand.
push arg : decrements the stack pointer and stores the data specified
as the argument into the location pointed to by the stack pointer.

eax : extended accumulator (for storage for intermediate results of arithmetic and logic)
esp : extended stack pointer (address of top of stack)
ebp : extended base pointer (address of bottom of current stack frame)

(Intel syntax)


This is the call to write_int() being made when PASS_BY_REF is defined.

    19: 	write_int(0x1234);
0043CE39 C7 85 38 FF FF FF 34 12 00 00 mov         dword ptr [ebp-0C8h],1234h 
0043CE43 8D 85 38 FF FF FF lea         eax,[ebp-0C8h] 
0043CE49 50                push        eax  
0043CE4A E8 1F CC FF FF    call        write_int (439A6Eh) 
0043CE4F 83 C4 04          add         esp,4 
    20: 	return 0;

In this case:
1. a temporary stack variable is set to 0x1234
2. the address of the temp variable is then read into the eax register
3. the address is pushed onto the stack
4. the function write_int() is called
5. when it returns, 4 bytes is popped of the stack

As you can see, a pointer is used to handle a reference parameter. As I used a 32-bit compiler, the pointer has a size of 4 bytes. This is what the final add is about: 4 bytes is being added to the stack pointer to pop the parameters back off (I compiled the code using the default __cdecl calling convention, so it's the caller's responsibility to clean up the stack.)

And this is the call when PASS_BY_REF is NOT defined.

    19: 	write_int(0x1234);
0043CE39 68 34 12 00 00   push        1234h 
0043CE3E E8 49 E0 FF FF   call        write_int (43AE8Ch) 
0043CE43 83 C4 04         add         esp,4 
    20: 	return 0;

Here you can see the value (0x1234) is pushed directly onto the stack.

4 bytes is popped off the stack in this case, too, as ints are also 32 bit. If I'd compiled a 64-bit binary, we would have seen 4 bits here and 8 bits in the by-ref case.

Andy

PS In the optimized case, write_int() (like all tiny functions) is inlined. And here, both the by-value and by-ref versions end up as identical machine code (you can see a value of 0x1234 being pushed onto the stack as part of the call of std::operator<<

    19: 	write_int(0x1234);
0040104E A1 3C 20 40 00   mov         eax,dword ptr [__imp_std::endl (40203Ch)] 
00401053 8B 0D 58 20 40 00 mov         ecx,dword ptr [__imp_std::cout (402058h)] 
00401059 50               push        eax  
0040105A 68 34 12 00 00   push        1234h 
0040105F 51               push        ecx  
00401060 E8 4B 01 00 00   call        std::operator<<<std::char_traits<char> > (4011B0h) 
00401065 83 C4 04         add         esp,4 
00401068 8B C8            mov         ecx,eax 
0040106A FF 15 48 20 40 00 call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402048h)] 
00401070 8B C8            mov         ecx,eax 
00401072 FF 15 40 20 40 00 call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402040h)] 
    20: 	return 0;

Last edited on
Thank you very much for this detailed and interesting reply! I really should learn to read assembler code. Any good book or website you would recommend?
It's not an area I know that well. What I've just described is pretty much the only kind of thing I've found useful; looking at what's going wrong in function calls and other places (e.g. dodgy code generation). I've never had call to do real assembly programming.

But the two books I own are:

"An introduction to Assembly Language Programming and Computer Architecture", by Joe Carthy (International Thomson Computer Press, 1996)

Might be a bit dated, but it was a better introduction than the Wrox book.

"Professional Assembly Language", David Blum (Wrox, 2005)

A much thicker, more inclusive book.

And then use online resources at Intel and elsewhere. But outside the Intel reference material, I have nothing I particularly favour.

IntelĀ® 64 and IA-32 Architectures Software Developer Manuals
http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html/

Hopefully an experienced assembly type will pass by? Otherwise, you'd best do a bit of googling!

Andy

PS Googling for "resources for intel assembly programmers" I found this:

X86 Assembly/Resources
http://en.wikibooks.org/wiki/X86_Assembly/Resources

might be useful?
Last edited on
Topic archived. No new replies allowed.