Symbol Visibility and Namespace

Symbol visibility and namespace

Before I answer your specific question, I should mention for others reading that applying symbol visibility attributes per namespace is a GCC-specific feature. MSVC only supports dllexport on classes, functions and variables, and if you want your code to be portable you must match MSVC there. As my original GCC symbol visibility guide (the one you linked to on the GCC website) points out, MSVC's macro based dllexport machinery can be easily reused to achieve something similar on GCC, so porting to MSVC will get you symbol visibility handling "for free".

Regarding your specific problem, GCC is correct to warn you. If an external user tried to use public type Bar they almost certainly need to use everything inside Bar, including Bar::foo. For the exact same reason all private member functions, despite being private, need to be visible. A lot of people are surprised at this, reasoning that private member function symbols are by definition inaccessible to anyone, but they forget that just because the programmer doesn't have access doesn't mean that the compiler doesn't need access. In other words, private member functions are private to you, but not the compiler. If they appear in a header file, that generally means the compiler needs access, even in an anonymous namespace (which are only anonymous to programmers, not to compilers which tend to use a hash of the contents as the "real" namespace name).

Hiding a namespace has very different effects to -fvisibility=hidden. This is because GCC spews out many symbols above and beyond those for a specific type e.g. for vtables, for type_info etc. -fvisibility=hidden hides stuff you can't hide by any compiler instructed way, and it's stuff absolutely essential to loading two binaries into the same process with colliding symbols e.g. two shared objects built using different versions of Boost.

I appreciate your attempts to fix the problems caused by broken symbol visibility in ELF and the consequences on broken C++ binaries and much lost programmer productivity. However you can't fix them - they are faults in ELF itself, which was designed for C and not C++. If it's any consolation, I wrote an internal BlackBerry white paper a few months ago on this topic as ELF symbol visibility problems are just as much a problem for us in BB10 as they are for any large corporation with a significant C++ codebase. So, maybe you might see some solutions proposed for C++17, especially if Doug Gregor's C++ Modules implementation makes good progress.

Symbol visibility and gcc warnings

In both cases, an explicit visibility attribute is the intended way to silence the warning on a per-class basis. From gcc/cp/decl2.c:

/* Don't warn about visibility if the class has explicit visibility.  */
if (CLASSTYPE_VISIBILITY_SPECIFIED (type))
vis = VISIBILITY_INTERNAL;

symbols visibility - shared library vs dll

Both POSIX and Windows provide different means to control symbol visibility. Source-code annotations (__attribute__((visibility("default"))) and __declspec(dllexport)) are the most common way to do this. Windows __declspec(dllexport) is indeed absolutely equivalent to POSIX -fvisibility=hidden + __attribute__((visibility("default"))) in this respect.

But both platforms also provide other alternatives to set visibility e.g. export symbol files and version scripts on POSIX and DEF files on Windows. These alternatives are less common (mainly because they are less portable and prevent some important optimizations).

So whether __declspec is "enough" and in what sense entirely depends on your project's buildscripts. If DEF files and linker scripts are not used - all symbols not explicitly marked with __declspec(dllexport) will be hidden.

Standard way to control symbol visibility

Use an anonymous namespace.

namespace
{
class C
{
C() {}
void f() {}
};

void f();
}

class ExportedClass
{
ExportedClass() {}
void f() {}
};

void exportedFunction() {}

As you can see, you should do this for normal functions, too. The usage of static for this is discouraged.

How to know the visibility of a symbol in an object file

The visibility is different from whether the symbol is local or global (which is what the lower-case/upper-case letters describe). A hidden symbol still can have external linkage, i.e. it is not limited to a translation unit.

I don't think nm has an option to show visibility, but you can use either

objdump -Ct lib.o

which will show an attribute .hidden if the symbol is hidden or

readelf -s lib.o

which has a column for the visibility (DEFAULT/HIDDEN).

Symbol visibility using g++

Public or hidden, symbols are still there. nm shows all symbols. The difference is that hidden symbols are not available to the dynamic linker, i.e. not exported and can not be interposed.

You might also like the following man gcc:

   -fvisibility=default|internal|hidden|protected
...
A good explanation of the benefits offered by ensuring ELF symbols
have the correct visibility is given by "How To Write Shared
Libraries" by Ulrich Drepper (which can be found at
<http://people.redhat.com/~drepper/>)---however a superior solution
made possible by this option to marking things hidden when the
default is public is to make the default hidden and mark things
public. This is the norm with DLL's on Windows and with
-fvisibility=hidden and "__attribute__ ((visibility("default")))"
instead of "__declspec(dllexport)" you get almost identical
semantics with identical syntax. This is a great boon to those
working with cross-platform projects.

How to use the __attribute__((visibility(default)))?

Is it possible to customize which functions are exposed by modifying the compilation command above?

No. Compilation option -fvisibility=[default|internal|hidden|protected]
(and note it is not a linkage option) makes the compiler attribute the specified dynamic visibility type to all global symbols
generated in the compilation unit except those that are specifically excluded by having a countervailing __attribute__((visibility(....)))
applied in the source code. Which makes the answer to your other question:

Is it possible to make area() public and set_values(int,int) local as shown in the first link without altering the code?

also No.

How would you change the source code to make Rectangle::area() dynamically
visible while all other global symbols are hidden for dynamic linkage by -fvisibility=hidden?
Here is a walk-through:

Let's start with:

rectangle.cpp (1)

class Rectangle {
int width, height;
public:
void set_values (int,int);
int area() {return width*height;}

};

void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}

and simply compile it to a PIC rectangle.o so:

$ g++ -Wall -c -fPIC rectangle.cpp

Then check the global symbol table:

$ nm -C rectangle.o
0000000000000000 T Rectangle::set_values(int, int)

Note that Rectangle::area isn't there. It's not available for
linkage at all, so the question of its dynamic visibility just does not arise.

That is because it is defined inline in the class definition and never called
in the compilation unit, so gcc need not even compile its definition. It vanishes
from the object file.

Rectangle::set_values, on the other hand, is not defined inline, so the compiler
emits a global symbol and definition.

To make Rectangle::area eligible for some visibility type, we first need to make
it a global symbol by not defining it inline:

rectangle.cpp (2)

class Rectangle {
int width, height;
public:
void set_values (int,int);
int area();

};

int Rectangle::area() {return width*height;}

void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}

Recompile and again check the global symbol table:

$ g++ -Wall -c -fPIC rectangle.cpp
$ nm -C rectangle.o
000000000000001a T Rectangle::set_values(int, int)
0000000000000000 T Rectangle::area()

Good. Now a global definition of Rectangle::area appears.

Next let's make a shared library librectangle.so from rectangle.o:

$ g++ -o librectangle.so --shared rectangle.o

Here are the Rectangle::* symbols in its global symbol table:

$ nm -C librectangle.so | grep 'Rectangle::'
00000000000005d4 T Rectangle::set_values(int, int)
00000000000005ba T Rectangle::area()

And here are the Rectangle::* symbols in its dynamic symbol table:

$ nm -CD librectangle.so | grep 'Rectangle::'
00000000000005d4 T Rectangle::set_values(int, int)
00000000000005ba T Rectangle::area()

They're the same.

Now let's hide those symbols for dynamic linkage. We need to recompile rectangle.cpp
then relink the shared library:

$ g++ -Wall -c -fPIC -fvisibility=hidden rectangle.cpp
$ g++ -o librectangle.so --shared rectangle.o

Here again are the Rectangle::* symbols now in the global symbol table:

$ nm -C librectangle.so | grep 'Rectangle::'
0000000000000574 t Rectangle::set_values(int, int)
000000000000055a t Rectangle::area()

They're the same as before.

And here are the Rectangle::* symbols now in the dynamic symbol table:

$ nm -CD librectangle.so | grep 'Rectangle::'; echo Done
Done

Now there are none, thanks to -fvisibility=hidden.

Finally, let's make just Rectangle::area dynamically visible, keeping all
the other global symbols dynamically hidden. We need to change the source code
again:

rectangle.cpp (3)

class Rectangle {
int width, height;
public:
void set_values (int,int);
__attribute__((visibility("default"))) int area();

};

int Rectangle::area() {return width*height;}

void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}

Then recompile and relink:

$ g++ -Wall -c -fPIC -fvisibility=hidden rectangle.cpp
$ g++ -o librectangle.so --shared rectangle.o

The global symbol table still shows:

$ nm -C librectangle.so | grep 'Rectangle::'
00000000000005a4 t Rectangle::set_values(int, int)
000000000000058a T Rectangle::area()

And the dynamic symbol table only shows:

$ nm -CD librectangle.so | grep 'Rectangle::'
000000000000058a T Rectangle::area()

Rectangle::area is now the only symbol that the shared library exposes for
dynamic linkage.

And before you go...

One thing more about:

Is it possible to make area() public and set_values(int,int) local as shown in the first link without altering the code?

Making a symbol hidden for dynamic linkage doesn't make it local. Dynamic visibility (default|internal|hidden|protected)
is an attribute of global symbols only. For linkage purposes, local symbols don't exist. The only ways to
make a symbol local that would otherwise be global are:-

  • In C or C++ source, qualify its definition with the static keyword
  • In C++ source, enclose its definition in an anonymous namespace

Then the symbol does not appear in the global, or dynamic, symbol tables.

Using GCC 4 symbols visibility feature on class methods

While it would appear the benefit exists, I would wait until the optimization becomes necessary to perform it.



Related Topics



Leave a reply



Submit