Getting a compiler error "undefined reference to vtable" - can't figure out why

Hi all,

Almost all "display" devices print THROUGH the Arduino core "Print.cpp".

I've written a small library that automatically and correctly (memory management wise) connects to the standard streams and provides stdin, stdout and stderr (so that I can use things like "printf" and "getc" directly).

My original version which worked perfectly, sent it's write calls and got it's read calls directly from the opened device. For example, if I "opened" the "Serial" object, the library worked like this:

MYSKETCH printf --> Serial.write
MYSKETCH getc <-- Serial.read

If I "open" an instance of LiquidCrystal, it works like this:

MYSKETCH printf --> LCD.write
MYSKETCH.getc <-- fails because an LCD doesn't read

So far, so good... but now what I want to do is have all output (printing) calls GO THROUGH the Arduino Print.cpp core code like "Serial.print" and "LCD.print" does.

Here's the current version of my modified code that's supposed to work, but gives me the compile error:

Stdinout.cpp

////////////////////////////////////////////////
/// NOTE Some pieces snipped out for clarity ///
////////////////////////////////////////////////
#include "Stdinout.h"

static Print *stream_ptr0 = NULL; // stdin stream pointer
static Print *stream_ptr1 = NULL; // stdout stream pointer
static Print *stream_ptr2 = NULL; // stderr stream pointer

// connect each stream to it's own device
void STDINOUT::open (Print &inpstr, Print &outstr, Print &errstr)
{
	close();  // close any that may be open

	if (stdin == NULL) { // open stdin
		stdin = fdevopen (NULL, getchar0);
		stream_ptr0 = &inpstr;
	}

	if (stdout == NULL) { // open stdout
		stdout = fdevopen (putchar1, NULL);
		stream_ptr1 = &outstr;
	}

	if (stderr == NULL) { // open stderr
		stderr = fdevopen (putchar2, NULL);
		stream_ptr2 = &errstr;
	}
}

// disconnect stdio from stream(s)
void STDINOUT::close (void)
{
	if (stderr) { // close stderr
		fclose (stderr);
		stderr = NULL;
		stream_ptr2 = NULL;
	}

	if (stdout) { // close stdout
		fclose (stdout);
		stdout = NULL;
		stream_ptr1 = NULL;
	}

	if (stdin) { // close stdin
		fclose (stdin);
		stdin = NULL;
		stream_ptr0 = NULL;
	}
}

// Function that fgetc, fread, scanf and related
// will use to read a char from stdin
int STDINOUT::getchar0 (FILE *fp)
{
	if (fp == stdin) {
		while (! (stream_ptr0->available()));  // wait until a character is available...
		return (stream_ptr0->read());  // ...then grab it and return
	} else {
		return _FDEV_ERR;
	}
}

// function that printf and related will use to write
// a char to stdout and auto-add a CR to a LF
int STDINOUT::putchar1 (char c, FILE *fp)
{
	if (fp == stdout) {
		if (c == '\n') { // if linefeed
			stream_ptr1->write ((char) '\r'); // also send CR
		}
		stream_ptr1->write ((char) c);
		return 0;
	} else {
		return _FDEV_ERR;
	}
}

// function that printf and related will use to write
// a char to stdout and auto-add a CR to a LF
int STDINOUT::putchar2 (char c, FILE *fp)
{
	if (fp == stderr) {
		if (c == '\n') { // if linefeed
			stream_ptr2->write ((char) '\r'); // also send CR
		}
		stream_ptr2->write ((char) c);
		return 0;
	} else {
		return _FDEV_ERR;
	}
}

STDINOUT STDIO; // Preinstantiate STDIO object

Stdinout.h

#ifndef STD_IN_OUT_H
#define STD_IN_OUT_H

#include <stdio.h>
#include "Print.h"

class STDINOUT : public Print
{
	public:
		void open (Print &);
		void open (Print &, Print &);
		void open (Print &, Print &, Print &);
		void close (void);
		Print &getStream (FILE *);

	private:
		static int getchar0 (FILE *); // char read for stdin
		static int putchar1 (char, FILE *); // char write for stdout
		static int putchar2 (char, FILE *); // char write for stderr
		virtual size_t write (uint8_t);
		using Print::write;
};

extern STDINOUT STDIO;

#endif // #ifndef STD_IN_OUT_H

I've got a simple test sketch which simply does this:

fprintf (stdout, "This is a test\n"); // inside setup/loop crap of course

This is the error message I get from the compiler:

[b]Arduino: 1.0.6 (Linux), Board: "atmega2560 (22.1M)"
/tmp/build1182073295482001577.tmp/core.a(Stdinout.cpp.o): In function `_GLOBAL__sub_I__ZN8STDINOUT4openER5Print':
Stdinout.cpp:(.text.startup._GLOBAL__sub_I__ZN8STDINOUT4openER5Print+0x8): undefined reference to `vtable for STDINOUT'
Stdinout.cpp:(.text.startup._GLOBAL__sub_I__ZN8STDINOUT4openER5Print+0xa): undefined reference to `vtable for STDINOUT'
collect2: error: ld returned 1 exit status[/b]

By the way, the IDE version listed is irrelevant since the AVR-GCC toolchain is this version:

[b]root@michael:/# avr-gcc -v
Using built-in specs.
Reading specs from /usr/local/lib/gcc/avr/6.3.0/device-specs/specs-avr2
COLLECT_GCC=avr-gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/avr/6.3.0/lto-wrapper
Target: avr
Configured with: ../configure --target=avr --enable-languages=c,c++ --disable-nls --disable-libssp --with-dwarf2
Thread model: single
gcc version 6.3.0 (GCC)[/b]

What the heck is a "vtable"?? I searched online, looked at other code for clues and I've been at it for two days straight now and can't seem to figure it out. I'm SURE it's a simple problem, and I'm sure I'll be kicking myself when I find out the simple, tiny mistake I'm making... but right now I just don't see it.

Anyone have any ideas? I'd really appreciate it, and if anyone has a test to try, let me know and I'll do it.

Thanks!

What the heck is a "vtable"?

Google "C++ vtable undefined reference"

AWOL:
Google "C++ vtable undefined reference"

Do you not think that this is one of the very first things I tried?

I saw all kinds of answers (especially concerning "missing constructors and destructors" and that these "need to have a body" (that is, "void CLASS::init()" actually need to have braces around it like "void CLASS::init() { }" which I think is a bunch of baloney (but I tried all these and a LOT more... no joy).

You should know by now that I'm not one of those "I can't make pin 13 blink can you help me" kind of people, nor do I just give up and post a question.

I've been up for two days straight working on this. My butt is sore, my mouth is dry, my eyes sting and my head hurts. I have been trying. Maybe it's simple and I'm just tired and don't see it.

Oh well......

So, presumably you do know what a vtable is by now?

AWOL:
So, presumably you do know what a vtable is by now?

When I wrote "what the heck is a vtable?" I was referring to what I thought the FIRST TIME I saw the error message.

krupski:
When I wrote "what the heck is a vtable?" I was referring to what I thought the FIRST TIME I saw the error message.

I'm not sure that sentiment was accurately conveyed by your post.

#include <Stdinout.h>

void setup() 
{
  fprintf (stdout, "This is a test\n"); // inside setup/loop crap of course
}

void loop() 
{
}
C:\ProjectsSplit\Arduino15\Sketch\libraries\Stdinout\Stdinout.cpp:58:26: error: 'class Print' has no member named 'available'

   while (! (stream_ptr0->available()));  // wait until a character is available...

                          ^

C:\ProjectsSplit\Arduino15\Sketch\libraries\Stdinout\Stdinout.cpp:59:24: error: 'class Print' has no member named 'read'

   return (stream_ptr0->read());  // ...then grab it and return

                        ^

exit status 1
Error compiling for board Arduino/Genuino Uno.

https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/Print.h

Yup. No available. Nor read.

By the way, the IDE version listed is irrelevant...

I disagree.

TL;DR, you need to code the write function.

WOW....I can't believe I didn't spot this 15 minutes ago. So here's the run-up.

Print is what is known in the jargon as an abstract class. An abstract class contains at least one function (exactly one in the specific case of Print) that is pure virtual. A pure virtual function is one with a prototype declared in the header, but no implementation is provided. For Print, the pure virtual function is virtual size_t write(uint8_t) = 0;.

You cannot create objects in the type of an abstract class. Abstract classes exist to support polymorphic behavior be being subclassed into different types (like Serial, LCD, or STDINOUT) that can provide the implementation-specific details behind a uniform interface.

When you inherit from an abstract class, you must provide an implementation (read: code) for every pure virtual function in the abstract class. If you don't, the new class is still abstract and cannot be constructed. You have not implemented the write function. Normally when a function is unimplemented you get "undefined reference to {FUNCTIONNAME}()" at the linker stage. Virtual functions give you the "Undefined reference to vtable..." error. It's the same cause, but because the functions are handled differently by the compiler the error text is different.

[quote author=Coding Badly link=msg=3288533 date=1496610079]Yup. No available. Nor read.
[/quote]
Different problem. Comment out those lines, and the vtable issue shows up in the linker stage.

Those errors occur because those functions are part of the Stream abstract class, which was not inherited by the STDINOUT class.

I'm not sure what code you used for Stdinout... if it was what I posted, that was the one that DOESN'T work and is giving me the grief. If you want to try it, grab THIS

Then try it as follows:

#include <Stdinout.h>

void setup (void)
{
    Serial.begin (115200);
    STDIO.open (Serial);
    printf ("It should work now\n");
}

void loop (void)
{
    // nothing here
}

You need to pass the device object to Stdinout (in this case, "Serial").

krupski:
I'm not sure what code you used for Stdinout...

Original post.

Latest IDE. Just installed.

I gave the answer in my essay post. Was it not noticed?

I noticed it. I even went on to determine that your analysis is spot-on but your conclusion has a minor flaw.

But that is neither here nor there because @krupski has every thing he needs to move past the problem.

Please, do tell.

Coding Badly:
@krupski has every thing he needs to move past the problem.

+1

The answer is in the first reply.
Nay, it's in the first result of the first reply.
Double Nay and Gadzooks, it's in the first answer of the first result of the first reply.

I'm guessing he modified Print.h, because neither posted class will compile. There is no available or read in the Print class. Period. This cannot compile:

static Print *stream_ptr0 = NULL; // stdin stream pointer
   ...
		while (! (stream_ptr0->available()));  // wait until a character is available...

With all the help I can muster,
/dev

P.S. Jiggy is close, but that problem usually leads to

       cannot declare variable 'STDIO' to be of abstract type 'STDINOUT'

Jiggy-Ninja:
Please, do tell.

class STDINOUT : public Print
{
...
	private:
		virtual size_t write (uint8_t);
		using Print::write;
};

At this point, STDINOUT is no longer abstract. Which means this...

You cannot create objects in the type of an abstract class.

...is not relevant and this...

Normally when a function is unimplemented you get "undefined reference to {FUNCTIONNAME}()" at the linker stage.

...is the expected outcome.

The cryptic message (undefined reference to vtable) is, from our perspective as people trying to use the toolset, a bug in the toolset.

-dev:
+1

The answer is in the first reply.
Nay, it's in the first result of the first reply.
Double Nay and Gadzooks, it's in the first answer of the first result of the first reply.

I disagree. Polymorphism was one of the more difficult OO concepts for me to wrap my head around. The fact that a problem you would recognize the normal error message for (missing function implementation) now has a different error message, mentioning a cryptic "vtable", makes things more confusing. Even if you do google it, learning new things is hard no matter how good you are.

I knew that the problem had to do with the pure virtual write function, and it still took me 15 minutes to connect the dots that "Oh, it's been defined but never declared".

P.S. Jiggy is close, but that problem usually leads to

       cannot declare variable 'STDIO' to be of abstract type 'STDINOUT'

Except STDINOUT is not an abstract type. It has a prototype for the write function that is not pure virtual (which satisfies the compiler), but an implementation for it is never provided (which makes the linker complain).

That quote wasn't meant to be a solution to the problem, but part of a background explanation of abstract classes.

...is the expected outcome.

Not it is not. That would be the result for a normal function, but size_t write(uint8_t) is a reimplemented virtual function. Virtual functions are referenced using vtables, that's how a base type reference can determine which descendant class's implementation to call. In this case, the linker doesn't have an implementation of the function to put in STDINOUT's vtable.

The cryptic message (undefined reference to vtable) is, from our perspective as people trying to use the toolset, a bug in the toolset.

How is it a bug? It's an error message with a known cause and known fix associated with an official feature of the C++ language.

Jiggy-Ninja:
That quote wasn't meant to be a solution to the problem, but part of a background explanation of abstract classes.

I never said it was. If you reread what I wrote you will find I implied exactly the opposite: that the flaw had nothing to do with a solution for @krupski.

Not it is not. ...

A class is either abstract or it is not. STDINOUT is not abstract.

The rest of what you wrote covers implementation details. Which, while interesting, are irrelevant. In the end STDINOUT is still not abstract.

How is it a bug?

The message is the bug. Which is why I wrote the phrase "is the expected outcome".

I have to assume that you are either not reading what I write or not understanding what I write. I will assume the latter is true and that I am failing to reasonably explain the flaw in your conclusion. I have done the best I can. Which means there is no reason for me to continue with this discussion.

[quote author=Coding Badly date=1496692827 link=msg=3289824]
I never said it was. If you reread what I wrote you will find I implied exactly the opposite: that the flaw had nothing to do with a solution for @krupski.[/quote]
The read and available issues are separate and unrelated to the vtable one. I commented them out and got the vtable error krupski reported. I chose to ignore those errors because you had already addressed them in reply #6.

A class is either abstract or it is not. STDINOUT is not abstract.

I know it is not, though I think I can see why you think that I think it is.

When you inherit from an abstract class, you must provide an implementation (read: code) for every pure virtual function in the abstract class. If you don't, the new class is still abstract and cannot be constructed.

Is it because of that? I'll admit that I didn't do the best job of wording that.

STDINOUT is not abstract because it does not have any pure virtual functions. We agree on that.

However, it does inherit from an abstract class (Print), so it has to provide an implementation for the pure virtual functions in order to have objects created. The compiler checks the class header and sees that it has a declaration for virtual size_t write (uint8_t);. This satisfies it that there will be an implementation for the function, and it goes on its merry way. Comment out that function declaration, and that's when you get that "cannot declare variable 'STDIO' to be of abstract type 'STDINOUT'" error that you mentioned.

So the compiler runs through all the files, keeping track of all the different function calls as it goes. Then it gets to the linker step, where it has to match those function calls with the actual code that they are supposed to call. And lo and behold, there's no STDINOUT::write function coded anywhere! It's not in the cpp file. So the linker errors out and says it can't find a function to put into the vtable.

The message is the bug. Which is why I wrote the phrase "is the expected outcome".

It is not a bug, but the expected error when you don't provide a body for a virtual function. What you think is the expected outcome is only correct for a non-virtual function.

Jiggy-Ninja:
It is not a bug, but the expected error when you don't provide a body for a virtual function. What you think is the expected outcome is only correct for a non-virtual function.

A quick test reveals...

cciuj8Yv.ltrans0.ltrans.o: In function main': cciuj8Yv.ltrans0.o:(.text.startup+0xa0): undefined reference to Child::write(char)'

...the linker outputs precisely what I expect it to output: an "undefined reference" to the missing member.

The linker fails to output its normal message in this specific situation. That not only makes it a bug but a great example of a boundary failure.