Am I Blind, Or Is This A Compiler Bug?

Today I made a very small modification to a library that has worked perfectly for many months. The change was to add a static boolean member variable name "DoTrace", which enables/disables the function of the rest of the class. I added the TraceOn() and TraceOff() methods to set/clear DoTrace, and put the guts of the TraceAppend method inside if (DoTrace) {}. On compile, this results in this warning:

MasterStateTraceRecord.cpp.o:In function MasterStateTraceRecord::TraceAppend(unsigned long, t_MasterStates, int, int)' MasterStateTraceRecord.cpp:186: warning: undefined reference to MasterStateTraceRecord::DoTrace'

Oddly, this message points to the last line of the TraceAppend method, while the reference to DoTrace is the first line of the method. The references to DoTrace in TraceOn() and TraceOff() do NOT generate this warning, nor to the accesses to the other static member data items within TraceAppend. "DoTrace" does not appear anywhere else within the project (which is about 40 source files...).

I'm stumped as to why this warning is being generated. Any ideas?

The .h File:

#ifndef MASTERSTATETRACERECORD_H
#define MASTERSTATETRACERECORD_H

#include "StateTraceRecord.h"
#include "MasterStates.h"

#define MASTER_TRACE_QUEUE_DEPTH	150

class MasterStateTraceRecord : public StateTraceRecord
{
	public:
	MasterStateTraceRecord();
	MasterStateTraceRecord(unsigned long timestamp, t_MasterStates state);
	virtual void Dump(Stream *console);
	const char *GetStateName(t_MasterStates state);
	static void TraceFlush(void);
	static void TraceAppend(unsigned long timestamp, t_MasterStates state, int pos, int pwm);
	static void TraceDump(Stream *console);
	static unsigned long GetStartTime(unsigned long time);
	static void TraceOn(void);
	static void TraceOff(void);
	
	t_MasterStates State;
	static boolean DoTrace;
	static const char *MasterStateStrings[MASTER_STATE_MAX+1];
	static MasterStateTraceRecord MasterTraceQueue[MASTER_TRACE_QUEUE_DEPTH];
	static int MasterTraceCnt;
	static boolean MasterTraceDone;
};
#endif

The .cpp File:

#include "StateTraceRecord.h"
#include "MasterStateTraceRecord.h"

const char *MasterStateTraceRecord::MasterStateStrings[MASTER_STATE_MAX+1] =
{
	STRINGIFY(MASTER_IDLE),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_1),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_2),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_3),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_4),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_5),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_6),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_7),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_8),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_9),
	STRINGIFY(MASTER_LOAD_FROM_CAROUSEL_10),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_1),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_2),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_3),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_4),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_5),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_6),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_7),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_8),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_9),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_10),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_11),
	STRINGIFY(MASTER_LOAD_TO_CAROUSEL_12),
	STRINGIFY(MASTER_LOAD_FROM_SPINDLE),
	STRINGIFY(MASTER_LOAD_FROM_SPINDLE_1),
	STRINGIFY(MASTER_LOAD_FROM_SPINDLE_2),
	STRINGIFY(MASTER_LOAD_FROM_SPINDLE_3),
	STRINGIFY(MASTER_LOAD_FROM_SPINDLE_4),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_1),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_2),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_3),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_4),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_5),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_6),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_7),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_8),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_9),
	STRINGIFY(MASTER_LOAD_TO_SPINDLE_10),
	STRINGIFY(MASTER_TO_PARK),
	STRINGIFY(MASTER_TO_PARK_1),
	STRINGIFY(MASTER_TO_PARK_2),
	STRINGIFY(MASTER_TO_PARK_3),
	STRINGIFY(MASTER_MOUNT_FIRST_TOOL),
	STRINGIFY(MASTER_LOAD_FIRST_TOOL_1),
	STRINGIFY(MASTER_UNMOUNT_LAST_TOOL),
	STRINGIFY(MASTER_UNLOAD_LAST_TOOL_1),
	STRINGIFY(MASTER_UNLOAD_LAST_TOOL_2),
	STRINGIFY(MASTER_LOAD_TOOL),
	STRINGIFY(MASTER_LOAD_TOOL_1),
	STRINGIFY(MASTER_LOAD_TOOL_2),
	STRINGIFY(MASTER_LOAD_TOOL_3),
	STRINGIFY(MASTER_SWAP_TOOL),
	STRINGIFY(MASTER_SWAP_TOOL_1),
	STRINGIFY(MASTER_SWAP_TOOL_2),
	STRINGIFY(MASTER_SWAP_TOOL_3),
	STRINGIFY(MASTER_PARK_ARM),
	STRINGIFY(MASTER_PARK_ARM_1),
	STRINGIFY(MASTER_PARK_ARM_2),
	STRINGIFY(MASTER_PARK_ARM_3),
	STRINGIFY(MASTER_PARK_ARM_4),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE_1),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE_2),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE_3),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE_4),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE_5),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE_6),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE_7),
	STRINGIFY(MASTER_INSERT_TO_SPINDLE_8),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL_1),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL_2),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL_3),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL_4),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL_5),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL_6),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL_7),
	STRINGIFY(MASTER_INSERT_TO_CAROUSEL_8),
	STRINGIFY(MASTER_HOME_ALL),
	STRINGIFY(MASTER_HOME_ALL_1),
	STRINGIFY(MASTER_HOME_ALL_2),
	STRINGIFY(MASTER_HOME_ALL_3),
	STRINGIFY(MASTER_HOME_ALL_4),
	STRINGIFY(MASTER_HOME_ALL_5),
	STRINGIFY(MASTER_HOME_ALL_6),
	STRINGIFY(MASTER_HOME_ALL_7),
	STRINGIFY(MASTER_RANDOM_SEEK_TEST),
	STRINGIFY(MASTER_DONE),
	STRINGIFY(MASTER_ERR),
	STRINGIFY(MASTER_STATE_MAX),
};

MasterStateTraceRecord MasterStateTraceRecord::MasterTraceQueue[MASTER_TRACE_QUEUE_DEPTH];
int MasterStateTraceRecord::MasterTraceCnt = 0;
boolean MasterStateTraceRecord::MasterTraceDone = false;


MasterStateTraceRecord::MasterStateTraceRecord()
{
	Device = TRACE_DEVICE_NONE;
	Timestamp = 0;
	State = MASTER_IDLE;
}


MasterStateTraceRecord::MasterStateTraceRecord(unsigned long timestamp, t_MasterStates state)
{
	Device = TRACE_DEVICE_MASTER;
	Timestamp = timestamp;
	State = state;
}


void MasterStateTraceRecord::TraceOn(void)
{
	DoTrace = true;
}
	

void MasterStateTraceRecord::TraceOff(void)
{
	DoTrace = false;
}
	

const char *MasterStateTraceRecord::GetStateName(t_MasterStates state)
{
	const char *ret = NULL;
	if (state < MASTER_STATE_MAX)
		ret = MasterStateStrings[state];
	return ret;
}


void MasterStateTraceRecord::Dump(Stream *console)
{
	char *s;
	if (State < MASTER_STATE_MAX)
	{
		if (GetStateName(State) != NULL)
			s = (char *)GetStateName(State);
	} else
		s = "Unknown";
	DoDump(console, s);
}


/**************************************************
 * MasterTraceFlush()
 *************************************************/
void MasterStateTraceRecord::TraceFlush(void)
{
	MasterTraceCnt = 0;
	MasterTraceDone = false;
}


/**************************************************
 * MasterTraceAppend()
 *************************************************/
void MasterStateTraceRecord::TraceAppend(unsigned long timestamp, t_MasterStates state, int pos, int pwm)
{
	if (DoTrace)
	{
		if (MasterTraceCnt < MASTER_TRACE_QUEUE_DEPTH)
		{
			MasterTraceQueue[MasterTraceCnt].Device = TRACE_DEVICE_MASTER;
			if (MasterTraceCnt == 0)
				MasterTraceQueue[MasterTraceCnt].Timestamp = timestamp;
			else
				MasterTraceQueue[MasterTraceCnt].Timestamp = timestamp - MasterTraceQueue[0].Timestamp;
			MasterTraceQueue[MasterTraceCnt].State = state;
			MasterTraceQueue[MasterTraceCnt].CurrentPos = pos;
			MasterTraceQueue[MasterTraceCnt].CurrentPWM = pwm;
			MasterTraceCnt++;
		}
		if (state == MASTER_IDLE)
			MasterTraceDone = true;
	}
}


/**************************************************
 * MasterTraceDump()
 *************************************************/
void MasterStateTraceRecord::TraceDump(Stream *console)
{
	unsigned long start;
	
	if (MasterTraceDone)
	{
		if (MasterTraceCnt)
		{
			console->println("Trace Begin");
			MasterTraceQueue[0].Timestamp = 0;
			for (int i=0; i<MasterTraceCnt; i++)
				MasterTraceQueue[i].Dump(console);
			console->println("Trace End");
			MasterTraceCnt = 0;
			MasterTraceDone = false;
		}
	} 
}


unsigned long MasterStateTraceRecord::GetStartTime(unsigned long time)
{
	unsigned long ret = time;
	
	if (MasterTraceCnt)
		ret = MasterTraceQueue[0].Timestamp;
	return ret;
}

Shouldn't you initialize the variable like you do with MasterTraceCnt and MasterTraceDone?

johnwasser:
Shouldn't you initialize the variable like you do with MasterTraceCnt and MasterTraceDone?

Yes, good catch. But, doesn't explain the warning...

Regards,
Ray L.

This is truly bizarre! I added initialize of DoTrace to both constructors:

MasterStateTraceRecord::MasterStateTraceRecord()
{
	Device = TRACE_DEVICE_NONE;
	Timestamp = 0;
	DoTrace = false;
	State = MASTER_IDLE;
}


MasterStateTraceRecord::MasterStateTraceRecord(unsigned long timestamp, t_MasterStates state)
{
	Device = TRACE_DEVICE_MASTER;
	Timestamp = timestamp;
	DoTrace = false;
	State = state;
}

This generates the warning twice - first for the default constructor, and again in TraceAppend(). If I comment out the line "DoTrace = false;" in the default constructor, then I get the warning only for TraceAppend(). I NEVER get the waning for the references to DoTrace in the second constructor, nor for TraceOn() or TraceOff()!

WTF???

Regards,
Ray L.

You have not provided a definition...

boolean MasterStateTraceRecord::DoTrace; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

MasterStateTraceRecord MasterStateTraceRecord::MasterTraceQueue[MASTER_TRACE_QUEUE_DEPTH];
int MasterStateTraceRecord::MasterTraceCnt = 0;
boolean MasterStateTraceRecord::MasterTraceDone = false;

In the immortal words of Homer Simpson: "D-Oh!" :slight_smile: Thanks!

But why does the compiler only give the warning for some of references, and not others???

And, why is it a warning, rather than an error?

Regards,
Ray L.

See Declaration vs Definition in C.

You declared DoTrace but never defined it. The lack of definition appears as an error at the linker stage.

In this test code I only get an error, not a warning:

class foo {
  public:
  foo () { bar = false; }  // constructor
  static bool bar;
};
void setup ()
  {
  foo::bar = false;
  }  // end of setup
void loop () { }

Very odd, because I only get the warning, no error, and it DOES do the link and create an object file. I'm still baffled by why it only flags some of the lines that reference the undefined variable, and not necessarily the first one, or even the second one, in the file. That's not the behavior I would have expected.

Regards,
Ray L.

I assume you are using a C++11 compiler. With previous compilers the lack of definition is an linker error...

C:\Users\CodingBadly\AppData\Local\Temp\build5439784863756191876.tmp\sketch_jul05a.cpp.o: In function `setup()':
C:\Arduino\arduino-1.6.5/sketch_jul05a.ino:10: undefined reference to `MasterStateTraceRecord::DoTrace'
collect2.exe: error: ld returned 1 exit status
Error compiling.

I know some restrictions have been relaxed with C++11. I assume this is one of them. That would explain the warning versus error.

On compile, this results in this warning:

Is the message from the compiler or the linker?

RayLivingston:
Thanks!

You are welcome.

The example Nick posted also fails for C++11.

An error only occurs if the undefined symbol is used. You can have as many undefined functions/variables you want, you just can't use them.

pYro_65:
The example Nick posted also fails for C++11.

An error only occurs if the undefined symbol is used. You can have as many undefined functions/variables you want, you just can't use them.

The error appears to come from the linker (as it references the o file), but it does NOT flag all places that USE the undefined variable. I used the variable in 5 places, it only flagged the first and fifth. When I commented out the first, it flagged only the fifth.

I am NOT using c++11.

Regards,
Ray L.

Everywhere that 'uses' the symbol, must also be used.

You must be able to trace from your main(), setup(), or loop() functions a call path to these methods. This is why commenting out the first only mentioned the fifth.

The compiler has not emitted a symbol into the object file, so the linker does not even search for it.

EDIT: This is an artifact of compiler optimizations like dead/redundant code removal. If you turn off all optimizations, you get a clearer picture of what is going on as everything must be defined in the class as the whole thing is compiled regardless of whats used inside it.

pYro_65:
This is an artifact of compiler optimizations like dead/redundant code removal. If you turn off all optimizations, you get a clearer picture of what is going on as everything must be defined in the class as the whole thing is compiled regardless of whats used inside it.

That makes perfect sense. I didn't realize this compiler did such fine-grained dead code removal.

Regards,
Ray L.