Link time error - undefined reference

I'm at a loss (once again). In a class method, I am calling a function of the Dallas Temperature library instance which I instantiated as "OneWireBus" in another routine.

Here is the instantiation:

1
2
3
 OneWire oneWire(PIN_IN_ONE_WIRE);
 DallasTemperature OneWireBus(&oneWire);
 OneWireBus.begin();


and here is the class method that is throwing the "undefined reference to `OneWireBus'" error during the linking.

1
2
3
4
5
6
7
8
9
10
template <size_t arraysize>
void OneWireSensor<arraysize>::AddData(void)
{
    extern OneWire oneWire;
    extern DallasTemperature OneWireBus;

    OneWireBus.requestTemperatures();

  // just this line above refers to OneWireBus but any OneWireBus methods I add here throw the same link-time error... no other warnings or errors.
}


No other error messages. Any ideas what I'm missing here?
THANKS!
Last edited on
You say you instantiated OneWireBus in another routine. I assume by "routine" you mean a function. That would make it a local variable that only exists inside that function. There is no way you can refer to it from other functions.

OneWireBus in the AddData function refers to a global instance (defined in the same namespace as the OneWireSensor class template is defined).
Last edited on
Hi Peter87,
I hear what you are saying but I have difficulty understanding why this throws no errors until the link time. If it were not in the namespace (via the extern) wouldn't I see error messages earlier in the compile/build process than at link time?

I also do not have this issue with any other libraries that I call from the larger "sensors" class of which "OneWireSensor" is a daughter.

Let me be more specific...

In main.cpp, I have setup() and loop() as you might expect. In setup(), I call initialize() which lives in initialize.cpp. It is in the initialize() function that I instantiate onewire and dallastemperature and start OneWireBus.

Now normally, these sorts of libraries are then globally available via the extern bringing them into the local namespace (right?). (For example, if I instantiated the OneWireSensor just in setup(), it would be available globally, so why not from initialize() which is called by setup() and especially after using the extern to bring it into the local namespace of this instance of AddData()?)

If I want OneWireBus to be available not only to the "sensors" class but to other functions, how would I do that if not this way?
So where did you define the variables oneWire and OneWireBus? As Peter87 mentioned they must be defined outside a function even outside of initialize() or loop() in order to use them within OneWireSensor<arraysize>::AddData().
PeteDD wrote:
wouldn't I see error messages earlier in the compile/build process than at link time?

No. because it could have been defined in another translation unit.

A translation unit is basically a .cpp file + the content of all header files that it includes (directly or indirectly). Each translation unit is compiled independently of other translation units. That's why it doesn't know until link time whether the variable has been defined or not.

Note that the main reason why people use the extern keyword is to be able to use variables that have been defined in another translation unit. It's seldom useful to use extern for a variable that you know are always defined in the same translation unit.

PeteDD wrote:
It is in the initialize() function that I instantiate onewire and dallastemperature and start OneWireBus.

In that case those two variables are local to the initialize() function and inaccessible from other functions (unless you pass them as argument or similar).

PeteDD wrote:
Now normally, these sorts of libraries are then globally available via the extern bringing them into the local namespace (right?).

I'm a bit confused by your use of the word "library". Do you mean variables?

extern just means it's a declaration and the definition is somewhere else (possibly in the same or a different translation unit).

It's only used for "global" variables that are defined outside of functions and classes.

PeteDD wrote:
For example, if I instantiated the OneWireSensor just in setup(), it would be available globally...

No. It would be a local variables that is destroyed as soon as it goes out of scope (e.g. when the function ends).

PeteDD wrote:
..., so why not from initialize() which is called by setup() and especially after using the extern to bring it into the local namespace of this instance of AddData()?

The order in which you call the functions doesn't affect what variables you can access.

extern OneWire oneWire; just tells the compiler that there is a global variable of type OneWire named "oneWire" (in the same namespace* as you're currently in) so that you can use that variable in the code even though it has been defined somewhere else. In other words, it means a specific variable (inside a specific namespace*) and not just any variable with that name.

* Note that when I write "namespace" I do not mean a class or function scope. I mean an actual C++ namespace declared using the namespace keyword or the global namespace with no name that everything is inside by default.
Last edited on
Thanks for all the lessons here folks. I really appreciate it. Unfortunately, this still is not computing for me and, if you will be patient with me, I will briefly explain why.

Recall that we have main.cpp (with setup() and loop() and where setup() calls initialize() which lives in initialize.cpp), initialize.cpp, and mysesonsors.cpp (where the AddData method lives).

In initialize() I start both DallasTemperature (as "OneWireBus") and Serial.
Now, if I go to AddData, I have no issue compiling and linking with a Serial.println() for example, so why can't I do the same with a OneWireBus.requestTemperature(), for example?

So...

in initialize()

1
2
3
4
5
6
7
8
void initialize(void)
{
  Serial.begin(USB_SERIAL_BAUD_RATE);

  OneWire oneWire(PIN_IN_ONE_WIRE);
  DallasTemperature OneWireBus(&oneWire);
  OneWireBus.begin();
}


and in mysensors.cpp is the allSensors class, we find the daughter method AddData:

1
2
3
4
5
6
7
8
template <size_t arraysize>
void OneWireSensor<arraysize>::AddData(void)
{
  OneWireBus.requestTemperatures(); // CODE WILL NOT LINK WITH THIS LINE

  Serial.println("just checking");  // BUT CODE WILL LINK WITH THIS LINE
}


What is the fundamental difference between what is happening with Serial and with OneWireBus????
Last edited on
OK... Here we go... The following works...

in main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifdef IN_ONE_WIRE
  OneWire oneWire(PIN_IN_ONE_WIRE);
  DallasTemperature OneWireBus(&oneWire);
#endif // IN_ONE_WIRE

void setup()
{
   initialize();
}

void loop
{
}


in initialize.cpp
1
2
3
4
5
6
7
void initialize(void)
{
    #ifdef IN_ONE_WIRE
        extern DallasTemperature OneWireBus;
        OneWireBus.begin();
    #endif // IN_ONE_WIRE
}


and, in mySensors.cpp
1
2
3
4
5
6
7
8
9
10
template <size_t arraysize>
void OneWireSensor<arraysize>::AddData(void)
{
    extern DallasTemperature OneWireBus;

    OneWireBus.requestTemperatures();

    this->m_DataArray[this->m_NumberOfSamples++] = OneWireBus.getTempC(this->m_OneWireAddress);
  
}
PeteDD wrote:
Now, if I go to AddData, I have no issue compiling and linking with a Serial.println() for example, so why can't I do the same with a OneWireBus.requestTemperature(), for example?

Because presumably Serial has been defined as a global variable, either by you or in some library you're using, and can therefore be used by any function in your whole program.

While OneWireBus is a local variable that only exists inside the initialize() function.
Last edited on
I understand now. This is a case of brain fade on my part after vacation.
Some things to be aware of:

In you last code snippet, you declare template function OneWireSensro::AddData().

First of all, it's dangerous to define a template function inside a .cpp file. You should put it in the header file. Code that references the template needs to expand the definition, and if the the definition is buried in a source file that's not being included, problems can ensue.

Also, the template function references global variables that are defined inside an #ifdef IN_ONE_WIRE block. If this template function gets expanded and compiled outside of an #ifdef IN_ONE_WIRE block, there will be a compilation error.

Note: due to the nature of templates, if calls to this function are also inside #ifdef IN_ONE_WIRE blocks, you will be OK. But without seeing the code that calls this function, I cannot verify this. Plus, better safe than sorry. Wrap applicable lines that require IN_ONE_WIRE definitions inside an appropriate #ifdef.
doug4 wrote:
First of all, it's dangerous to define a template function inside a .cpp file.

What's dangerous about it? ODR violations?

It's usually not what you want but in my experience the error you get is pretty harmless (although perhaps not so easy to understand if you don't know about this limitation).

For private member function templates it's usually not a problem assuming you define them in the same .cpp file as all the other member functions.
Last edited on
doug4 wrote:
If this template function gets expanded and compiled outside of an #ifdef IN_ONE_WIRE block, there will be a compilation error.

Macros are expanded before normal compilation starts so what matters is whether IN_ONE_WIRE is defined when the preprocessor stage reaches a #ifdef IN_ONE_WIRE block. Where and when the template is instantiated doesn't matter.

I don't know the purpose of the IN_ONE_WIRE macro and whether the #ifdef IN_ONE_WIRE blocks will cause problems but it looks a little bit suspicious that the other places that use OneWireBus use these #ifdef IN_ONE_WIRE blocks but not AddData.
Last edited on
Topic archived. No new replies allowed.