Is private member hacking defined behaviour?
Asked Answered
K

6

23

I have the following class:

class BritneySpears
{
  public:

    int getValue() { return m_value; };

  private:

    int m_value;
};

Which is an external library (that I can't change). I obviously can't change the value of m_value, only read it. Even deriving from BritneySpears won't work.

What if I define the following class:

class AshtonKutcher
{
  public:

    int getValue() { return m_value; };

  public:

    int m_value;
};

And then do:

BritneySpears b;

// Here comes the ugly hack
AshtonKutcher* a = reinterpret_cast<AshtonKutcher*>(&b);
a->m_value = 17;

// Print out the value
std::cout << b.getValue() << std::endl;

I know this is bad practice. But just out of curiosity: is this guaranteed to work? Is it defined behaviour?

Bonus question: Have you ever had to use such an ugly hack?

Edit: Just to scare fewer people: I don't intend to actually do this in real code. I'm just wondering ;)

Katharynkathe answered 14/5, 2010 at 13:18 Comment(12)
This code could be optimized greatly by just having BritneySpears::getValue() and AshtonKutcher::getValue() simply return 0.Sulfanilamide
@Syntactic: I knew someone would point that out :P Do I really need to add a constructor to make my question error-free ?! :DKatharynkathe
possible duplicate of Accessing private membersPotential
@ereOn: Oh, I'm not clever enough to point out errors in C++ code. I was referring to the fact that if these classes correctly modeled the actual Britney Spears and Ashton Kutcher, they would have to have a value of 0.Sulfanilamide
@Syntactic: If the classe correctly modeled the actual Britney Spears, all her members would have been public ;)Katharynkathe
Why do you want to access Britney Spears' private parts? (I'm sorry. I tried really hard, but I just couldn't resist.)Checkoff
@sbi: I could have choose A and B but I knew it would have been less much fun :DKatharynkathe
@ereOn: And it would keep spawning child processes and performing unsafe operations with them.Sulfanilamide
Possible duplicate: #424604Gaitan
+1 for using BritneySpears in an example with undefined behavior.Arbela
+1 for getting Britney's privates and "ugly hack" in the same sentence.Foreboding
So many geek jokes about Britney Spears: i love it !Katharynkathe
P
21

This is undefined behaviour. The members within each access-qualifier section are guaranteed to be laid out in the order they appear, but there is no such guarantee between acccess qualifiers. For instance, if the compiler chooses to place all private members before all public members, the above two classes will have a different layout.

Edit: Revisiting this old answer, I realized that I missed a rather obvious point: the struct definitions have exactly one data member each. The order of member functions is irrelevant, since they don't contribute to the class's layout. You might well find that both data members are guaranteed to be in the same place, though I don't know the standard well enough to say for sure.

But! You cannot dereference the result of reinterpret_casting between unrelated types. It's still UB. At least, that's my reading of http://en.cppreference.com/w/cpp/language/reinterpret_cast, which is a gnarly read indeed.

Philps answered 14/5, 2010 at 13:25 Comment(7)
So what If every member of BritneySpears was private, and every member of AshtonKutcher was public ? Would the order become guaranteed ?Katharynkathe
@ereOn: Only if they were all in a single access-qualifier section. Having two sections, both private, still results in an unspecified memory layout.Philps
I still wonder how one can learn this without asking on SO. Thanks for the lesson ! :DKatharynkathe
@Katharynkathe For stuff like this, the only way to know for sure is to read and understand the standard.Tonry
Even if the members were typographically identical in each class, you still could not guarantee the actual memory layout if they are compiled with different compiler options or different compilers.Waggle
@Tonry I still don't master english good enough to be able to read such a standard without catching a serious headache.Katharynkathe
@Loadmaster: Good observation. My answer assumes the same compilation environment. Of course, one must assume this in order to get just about any program bigger than one compilation unit to work. The problem with mixing and matching access-qualifiers is that the layout can be different even in a single compilation unit, let alone across multiple compilation units with identical environments.Philps
S
9

This is undefined behavior, for the reasons Marcelo pointed out. But sometimes you need to resort to such things when integrating external code you can't modify. A simpler way to do it (and equally undefined behavior) is:

#define private public
#include "BritneySpears.h"
Stringy answered 14/5, 2010 at 13:30 Comment(9)
we can have private members without the label "private" so this may not work ;)Brooke
@Nick D: Works on structs though (?)Ogrady
@Nick: #define class struct. Not that I'd ever do anything like that.Froze
This still doesn't solve the problem of object layout. If the library was built with a different compiler, then the layout of the BritneySpears class in the library is already set: m_value is in the private section, and there's no amount of messing with the header that will change that. So although you can trick your compiler into building your code with this hack, you still have the problem of incompatibility if your compiler and the compiler used to build the library do not lay out their public and private sections the same way.Borough
By the way, I know that you (Kristo) understand that this is undefined behavior, I just thought that this was worth pointing out explicitly.Borough
@Tyler, that's good info. Although, aren't you pretty well hosed in general if you mix compilers like that? C++ doesn't have a defined ABI after all.Stringy
@Mike, oh yeah, #define class struct, #define private public is a lethal weapon!Brooke
Add #define protected publicCherrylchersonese
You are not allowed to #define c++ keywords, thus #define private public isn't legal c++Pompano
R
4

You might not be able to modify the library for BritneySpears, but you should be able to modify the .h header file. If so, you can make AshtonKutcher a friend of BritneySpears:

class BritneySpears 
{
    friend class AshtonKutcher;
  public: 

    int getValue() { return m_value; }; 

  private: 

    int m_value; 
}; 

class AshtonKutcher 
{ 
  public: 

    int getValue(const BritneySpears & ref) { return ref.m_value; }; 
}; 

I can't really condone this trick, and I don't think I've ever tried it myself, but it should be legal well-defined C++.

Royston answered 14/5, 2010 at 15:27 Comment(1)
Who would want to be friends with Britney Spears?Irvinirvine
L
2

@Marcelo has it right: the order of members is undefined across different access levels.

But consider the following code; here, AshtonKutcher has exactly the same layout as BritneySpears:

class AshtonKutcher
{
  public:
    int getValue() { return m_value; };
    friend void setValue(AshtonKutcher&, int);

  private:
    int m_value;
};

void setValue(AshtonKutcher& ac, int value) {
    ac.m_Value = value;
}

I believe that this may actually be valid C++.

Latreese answered 14/5, 2010 at 13:37 Comment(1)
Well, we have no guarantee that the compiler will order the different sections in the same order for two different classes. Well, I can't see which implementation would act like that, but still.Katharynkathe
R
2

There is an issue with your code, underlined by the answers. The problem comes from ordering the values.

However you were almost there:

class AshtonKutcher
{
public:

  int getValue() const { return m_value; }
  int& getValue() { return m_value; }

private:
  int m_value;
};

Now, you have the exact same layout because you have the same attributes, declared in the same order, and with the same access rights... and neither object has a virtual table.

The trick is thus not to change the access level, but to add a method :)

Unless, of course, I missed something.

Did I precise it was a maintenance nightmare ?

Restore answered 14/5, 2010 at 16:51 Comment(0)
E
1

Use of reinterpret_cast should usually be avoided and it's not guaranteed to give portable results.

Also, why do you want to change the private member? You could just wrap the original class in a new one (prefer composition over inheritance) and handle the getValue method as you wish.

Estuary answered 14/5, 2010 at 13:27 Comment(2)
Well, I was just wondering. I don't intend to this in any real code ;)Katharynkathe
Since the method getValue is not virtual, you will not be able to change its behavior in existing code by extending the class (all existing references at base level will call the base method, and the method --not really overriding-- in the derived class will never be called)Zeiler

© 2022 - 2024 — McMap. All rights reserved.