3

I have a C++ class; this class is as follows:

First, the header:

class PageTableEntry {
public:

    PageTableEntry(bool modified = true);
    virtual ~PageTableEntry();

    bool modified();
    void setModified(bool modified);

private:
    PageTableEntry(PageTableEntry &existing);
    PageTableEntry &operator=(PageTableEntry &rhs);

    bool _modified;
};

And the .cpp file

#include "PageTableEntry.h"

PageTableEntry::PageTableEntry(bool modified) {
    _modified = modified;
}

PageTableEntry::~PageTableEntry() {}

bool PageTableEntry::modified() {
    return _modified;
}
void PageTableEntry::setModified(bool modified) {
    _modified = modified;
}

I set a breakpoint on all 3 lines in the .cpp file involving _modified so I can see exactly where they are being set/changed/read. The sequence goes as follows:

  1. Breakpoint in constructor is triggered. _modified variable is confirmed to be set to true
  2. Breakpoint in accessor is triggered. _modified variable is FALSE!

This occurs with every instance of PageTableEntry. The class itself is not changing the variable - something else is. Unfortunately, I don't know what. The class is created dynamically using new and is passed around (as pointers) to various STL structures, including a vector and a map. The mutator is never called by my own code (I haven't gotten to that point yet) and the STL structures shouldn't be able to, and since the breakpoint is never called on the mutator I can only assume that they aren't.

Clearly there's some "gotcha" where private variables can, under certain circumstances, be changed without going through the class's mutator, triggered by who-knows-what situation, but I can't imagine what it could be. Any thoughts?

UPDATE: The value of this at each stage:
Constructor 1: 0x100100210
Constructor 2: 0x100100400
Accessor 1: 0x1001003f0
Accessor 2: 0x100100440

UPDATE2:
(code showing where PageTableEntry is accessed)

// In constructor:
    _tableEntries = std::map<unsigned int, PageTableEntry *>();

// To get an entry in the table (body of testAddr() function, address is an unsigned int:
    std::map<unsigned int, PageTableEntry *>::iterator it;
    it = _tableEntries.find(address);
    if (it == _tableEntries.end()) {
        return NULL;
    }
    return (PageTableEntry *)&(*it);

// To create a new entry:
    PageTableEntry *entry = testAddr(address);
    if (!entry) {
        entry = new PageTableEntry(_currentProcessID, 0, true, kStorageTypeDoesNotExist);
        _tableEntries.insert(std::pair<unsigned int, PageTableEntry *>(address, entry));
    }

Those are the only points at which PageTableEntry objects are stored and retrieved from STL structures in order to cause the issue. All other functions utilize the testAddr() function to retrieve entries.

UNRELATED: Since C++ now has 65663 questions, and so far 164 have been asked today, that means that only just today the number of C++ tagged questions exceeded a 16-bit unsigned integer. Useful? No. Interesting? Yes. :)

jstm88
  • 3,335
  • 4
  • 38
  • 55
  • This could be an instance of the C++ ***As-If-Broken*** rule. That's the rule that allows a compiler to turn a correct program into an incorrect one, especially when optimizing. – jww Jun 04 '16 at 21:39

5 Answers5

9

Either your debugger isn't reporting the value correctly (not unheard of, and even expected in optimized builds) or you have memory corruption elsewhere in your program. The code you've shown is more-or-less fine and should behave as you expect.


EDIT corresponding to your 'UPDATE2':
The problem is in this line:

return (PageTableEntry *)&(*it);

The type of *it is std::pair<unsigned const, PageTableEntry*>&, so you're effectively reinterpret-casting a std::pair<unsigned const, PageTableEntry*>* to a PageTableEntry*. Change that line to:

return it->second;

Keep an eye out for other casts in your codebase. Needing a cast in the first place is a code smell, and the result of doing a cast incorrectly can be undefined behavior, including manifesting as memory corruption as you're seeing here. Using C++-style casts instead of C-style casts makes it trivial to find where casts occur in your codebase so they can be reviewed easily (hint, hint).

ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • It's not optimized, and it _could_ be that the debugger isn't reporting it correctly (I'm using Clang/LLDB). BUT: the program's logic depends on the value, and the program fails in exactly the way it should if the variable is improperly initialized to false. So the code utilizing the class is getting the false value as well. I also tried using GCC/GDB and it reported exactly the same phenomenon... – jstm88 Apr 11 '11 at 17:18
  • @jfm429 : Fair enough. Everything you say sounds more and more like memory corruption... – ildjarn Apr 11 '11 at 17:19
  • Yep, that was it. *derp* I will say that C -style casts are sometimes beneficial and even necessary, but clearly in this case they didn't help. :) – jstm88 Apr 11 '11 at 21:16
4

std::map<>::find() returns an iterator which when dereferenced returns a std::map<>::value_type. The value_type in this case is a std::pair<>. You're returning the address of that pair rather than the PageTableEntry. I believe you want the following:

// To get an entry in the table (body of testAddr() function, address is an unsigned int:
std::map<unsigned int, PageTableEntry *>::iterator it;
it = _tableEntries.find(address);
if (it == _tableEntries.end()) {
    return NULL;
}
return (*it).second;

P.S.: C-style casts are evil. The compiler would have issued a diagnostic with a C++ cast in place. :)

Void - Othman
  • 3,441
  • 18
  • 18
  • Ah well, it looks like @ildjarn beat me to the punch while I was typing my answer. +1 ildjarn :) – Void - Othman Apr 11 '11 at 18:08
  • I +1'd you for "C-style casts are evil" ;-] – ildjarn Apr 11 '11 at 18:10
  • And +1 for a correct (if a hair late) answer as well. Also, by C++ cast, do you mean dynamic_cast? Most of my experience is in C (where the C-style cast is obviously the only way) and Objective-C (where casting is only necessary for style). I've also had people tell me that pointers in C++ are evil and that smart pointers are the _only_ correct way, so... :P – jstm88 Apr 11 '11 at 21:25
  • 1
    @jfm429: Thanks for the +1 :). `dynamic_cast` is one of the standard C++ casts. The others are `static_cast`, `const_cast` and `reinterpret_cast`. For example, if you replaced your C-style cast with `static_cast(&(*it))` the compiler would choke on the incompatible type casting. The C-style cast disables all type checking and lets you essentially do whatever you want, which is why you didn't get an error. There is plenty of information on the Web about the standard C++ casts. Regarding smart pointers, I prefer them heavily, too. :) – Void - Othman Apr 11 '11 at 22:20
3

Try looking at the value of this at each breakpoint.

paperjam
  • 8,321
  • 12
  • 53
  • 79
  • Hmm... `this` changes (see update in main description) - even though the copy/assignment operators are intentionally made private. – jstm88 Apr 11 '11 at 17:15
0

That copy constructor and assignment operator are both going to be used a lot if you're using STL containers. Maybe if you show us the code for those, we would see something wrong.

Ernest Friedman-Hill
  • 80,601
  • 10
  • 150
  • 186
  • I suspect the copy constructor and assignment operator are intentionally left undefined, to make the class non-copyable and non-assignable. – ildjarn Apr 11 '11 at 17:05
  • @Xeo : `const`-ness of the argument is not a factor; those are in fact the class' copy c'tor and assignment operator declarations. §12.8/2 in the C++03 standard reads: "*A non-template constructor for class `X` is a copy constructor if its first parameter is of type `X&`, `const X&`, `volatile X&` or `const volatile X&`, and either there are no other parameters or else all other parameters have default arguments.*" – ildjarn Apr 11 '11 at 17:06
  • I intentionally made the copy/assignment constructors private (and there's no implementation); for the program to function the class CANNOT be copied. Ever. However, see the update I posted above - it looks like the class is indeed being copied, somehow... – jstm88 Apr 11 '11 at 17:13
  • @jfm429 : It looks more like memory corruption to me, given that the class is non-copyable. You'll need to post more code showing how the class is used if you want further feedback. – ildjarn Apr 11 '11 at 17:17
  • Code posted: every creation/retrieval of entries is handled by those code segments, encapsulated in methods. Entries are never deleted until the program terminates. – jstm88 Apr 11 '11 at 17:29
  • @ildjarn: Oh, that's nice to know. Thank you for that quote. :) And sorry, I overlooked that `private` keyword there... – Xeo Apr 11 '11 at 17:40
  • @jfm429 : I updated my answer to respond to your new addendum. – ildjarn Apr 11 '11 at 17:48
0

Could you add another unique value to the class to track the PageTableEntry s? I know that I've had problems like this where the real problem was that there were multiple entries that looked the same and the breakpoints might switch PageTableEntry s without me realizing it.

Bryan Rosander
  • 135
  • 1
  • 6