I reading this page.
I follow the answer step by step to test, alse add -fvisibility=hidden
to make all symbols hidden, and then I extended the code that is in the answer.
//rectangle.h
#pragma once
#include <memory>
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area();
};
std::shared_ptr<Rectangle> __attribute__((visibility("default"))) get_rectangle();
//rectangle.cpp
#include "rectangle.h"
int Rectangle::area() {return width*height;}
void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}
std::shared_ptr<Rectangle> __attribute__((visibility("default"))) get_rectangle() {
return std::make_shared<Rectangle>();
}
I build librectangle.so, then I code main.cpp to link librectangle.so
//main.cpp
#include "../rectangle/rectangle.h"
int main() {
auto rectangle = get_rectangle();
rectangle->set_values(2, 3);
rectangle->area();
return 0;
}
Compilation error
undefined reference to `Rectangle::set_values(int, int)'
undefined reference to `Rectangle::area()'
But, If I change the class member function to a virtual function, it will compile correctly
//changed rectangle.h
#pragma once
#include <memory>
class Rectangle {
int width, height;
public:
virtual void set_values (int,int);
virtual int area();
};
std::shared_ptr<Rectangle> __attribute__((visibility("default"))) get_rectangle();
Use Use
nm -C librectangle.so
to compare the normal function class and the virtual function class. The left is the normal function class. the right is the virtual function class.
nm -C librectangle.so | grep Rectangle
nm -C librectangle.so | grep area
nm -C librectangle.so | grep set_values
both demos add -visibility=hidden
, the normal functions compile incorrectly, but the virtual functions compile correctly?
An library has a list of symbols (that is, functions and global variables) it contains, the exported symbols. An object file has a list of symbols it uses that aren't defined in that object file, the imported symbols.
Hidden visibility on a symbol means it isn't added to the list of exported symbols. Default visibility means it is.
When the linker creates a library/executable from a bunch of object files, it looks at the imported symbols of all the object files and resolves them according to the exported symbols of all object files and libraries.
Let's look at the first example. Your main.o is compiled to assembly roughly to this effect (assuming no callee-saved registers except stack pointer):
As you can see, it references three symbols that are used:
get_rectangle
,Rectangle::set_values
andRectangle::area
. But onlyget_rectangle
has default visibility, so the other two are not listed as exports in librectangle.so, so the linker can't find them. You get errors.But that's not how virtual calls work. There, the code looks something like this:
As you can see, now the assembly only references a single symbol,
get_rectangle
. The others are only called through the pointers stored in the vtable. The vtable is a global constant stored in the librectangle.so, so it has access to the hidden functions. Also, the vtable itself is a hidden symbol, but it is only referenced by the constructor ofRectangle
, which is implicitly defined by the compiler. It is an inline function and so exists in every compilation unit that includes the header, but the only place it is used is inget_rectangle
, so there it has access to the vtable symbol.Note that if you create a local variable of type
Rectangle
inmain
you will get a linker error referring to the vtable missing. Also, since the compiler might devirtualize the calls, you would also get errors for these symbols again as it tries the usual direct calls.