Reputation: 371
Lets assume the following header foo.h:
class Foo {
private:
void print() const;
};
and following foo.cpp:
#include <iostream>
#include "foo.h"
void Foo::print() const {
std::cout << "Secret" << std::endl;
}
another header foo1.h, that is the same as foo.h unless method print is declared public:
class Foo {
public:
void print() const;
};
and this will be main.cpp, that just call print in foo1.h:
#include "foo1.h"
int main() {
Foo f;
f.print();
return 0;
}
What seems strange for me is that the following linking gonna work:
g++ foo.cpp -c -o foo.o
g++ main.cpp -c -o main.o
g++ main.o foo.o -o exec
./exec
The last command will output:
Secret
So without knowing the concrete implementation of class Foo but, knowing its declaration and having its object file, we can create situation when its methods can be used even though they are declared private.
My questions are:
Why does it work? Linker doesn't consider private and public declarations?
Is this behavior useful in practice? If yes, how is it used? My guess that it could be useful for testing.
Upvotes: 2
Views: 159
Reputation: 238401
Standard, section [basic.def.odr]:
There can be more than one definition of a class type [snip] in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then
— each definition of D shall consist of the same sequence of tokens; and
Your program violates this rule, because the two definitions of the class do not consist of the same sequence of tokens. Violating the one-definition rule makes the program ill-formed.
- Why does it work?
Being ill-formed, the standard doesn't specify, how this situation should be handled. The tool chain is free to refuse to link the program, but it is allowed to link successfully. Your linker happens to do the latter. Another linker might do the former.
Linker doesn't consider private and public declarations?
As you have observed through your experiment, your linker appears to not consider access specifiers. A linker doesn't need to consider them. They are purely a compile time concept.
Is this behavior useful in practice? If yes, how is it used?
Relying on this is evil and non-portable. That said, it can sometimes be seen in the wild as a dirty hack to work around API limitations when the source is not available for recompilation.
Upvotes: 3
Reputation: 340316
First off, since you're violating the "One Definition Rule" (C++11 3.2/5 "One definition rule" says that separate class definitions in different translations units must "consist of the same sequence of tokens"), anything goes as far as the toolchain is concerned. it could diagnose an error, or produce a program that appears to work (as in your test).
A simple reason why your experiment produces the results that you see is that the access to a class member is 'enforced' by the compiler, and you have told the compiler that the the access to member Foo::print()
is public.
It is conforming for the toolchain to encode the access for a member in the name mangle that is performed for other reasons (such as overloading). However, since the standard doesn't require that the toolchain enforce it, it seems that implementers decided that they didn't need to account for access control at link time. In other words, I think that it would be feasible to encode access control into the external symbol that the linker uses, but that work wasn't done; probably because it's not necessary strictly speaking.
Note that Microsoft C++ does incorporate the access to a member in the external name, so you do get a link time error:
testmain.obj : error LNK2019: unresolved external symbol "public: void __thiscall Foo::print(void)const " (?print@Foo@@QBEXXZ) referenced in function _main testmain.exe : fatal error LNK1120: 1 unresolved externals
Here are the symbols g++ produces (along with a c++filt
decode):
D:\so-test>nm test.o | grep Foo
000000000000008c t _GLOBAL__sub_I__ZNK3Foo5printEv
0000000000000000 T _ZNK3Foo5printEv
D:\so-test>nm testmain.o | grep Foo
U _ZNK3Foo5printEv
D:\so-test>c++filt _ZNK3Foo5printEv
Foo::print() const
And here are the symbols MS C++ produces (along with a decode):
D:\so-test>dumpbin /symbols test.obj | grep Foo
22D 00000000 SECTBA notype () External | ?print@Foo@@ABEXXZ (private: void __thiscall Foo::print(void)const )
D:\so-test>dumpbin /symbols testmain.obj | grep Foo
009 00000000 UNDEF notype () External | ?print@Foo@@QBEXXZ (public: void __thiscall Foo::print(void)const )
Upvotes: 4
Reputation: 1294
The linker only resolves symbols. Each C++ file is compiled independently, with whatever declarations are brought in via #include statements, to build up symbols (in a .o file), then the linker just works on the compiler output. C++ access modifiers such as private, protected, and public affect only the compiler, not the linker. (Technically, the linker isn't even aware of classes, it just deals with decorated/mangled symbol names for class members.) It's possible, though not recommended, to modify declarations of classes to change the access of members, to in effect "un-hide" them.
Upvotes: 1