Dagob
Dagob

Reputation: 745

Copy part of a class

I want to copy a part of a class to a buffer. I need this to make something that can look if there are changes in a class, and send it over the network to update the changes on the server.

I made a template class that can back-up and restore classes. Now I am making the "look for differences" function. I want that the user can define how big the memory blocks will be. This split the class in parts and takes less data to send.

The problem is that I can't copy a part of the class memory to the heap, I can't get the address correct. If I do "address of the class" + "0x04". Then I don't get the correct addres.

This is an exaple that I made:

testclass test1;
testclass test2;

test1.set(1,1);
test2.set(1,2);

cout << &test1 << " " << (&test1 + 0x04) << endl; //0018FF24 0018FF44

memcpy(&test2,&test1 + 0x04,4);

test2.echo(); //Wrong data!

The header:

class testclass
{
    int test,test2;

public:
    void set(int a, int b) {test = a, test2 = b;}
    void echo() {cout << test << " " << test2 << endl;}
};

I hope someone help me with this problem.

Thanks!

Upvotes: 2

Views: 232

Answers (4)

Andrew D.
Andrew D.

Reputation: 1022

1) At first, you need your class to be POD. At least you can take out data members to separate structure.

2) Then, if choose your offset granularity = 1, 2, 4 or 2^n bytes as necessary; and determine the type that suits this requirement: 2^n == sizeof(chunk_type). For example, you wish to byte-to-byte comparison, so cast you pointer to _State (or MyClass see #1) to desired type: (char*)this->m_state.

Here is the function, that tries to find the first chunk that differs in both classes, and returns its offset or -1 if no differences found.

class MyClass {
  struct _State { 
    int a,b
  };

  _State m_state;

public:
  typedef char  chunk_type;
  int next_diff_pos(const MyClass& other, size_t offset = 0) const {
      chunk_type *pThis = &m_state, 
                 *pOther = &other.m_state;

      if (offset < sizeof(_State)) {
        size_t n = offset;
        while(*pThis++ == *pOther++ && n < sizeof(_State)) 
          n++;
        // We need to test this again, because of ambigous while condition
        if (n < sizeof(_State))
          return n;
      } 

      return -1;
  }
};

PS: Of course, your chunk_type must have == operator defined (this done already for char, int and other scalars).

(I've not tested the code)

Upvotes: 0

Steve Jessop
Steve Jessop

Reputation: 279305

&test1 + 0x04 means to add 4 times the sizeof testclass to the address of test1, and the resulting pointer has type testclass*. It would point to the fifth element of an array whose first element is at the address of test1, except that test1 isn't part of an array, so the addition has undefined behavior.

What you seem to want is to add 4 bytes to the address of test1. You can do that for example with ((char*)&test1) + 4, which results in a pointer of type char*. Beware that the standard doesn't guarantee that sizeof(int) is 4, nor does it guarantee that offsetof(testclass, test2) == sizeof(int).

You're permitted to inspect any object's memory as char* or unsigned char*. But there are some limits on how useful this ability is:

  1. Classes can have padding bytes in them, that can take any values. So just because two objects have different byte values in memory doesn't mean they aren't equal.
  2. Non-POD classes can have pretty much arbitrary "extra stuff" in them, put there by the implementation, and cannot safely be copied byte-wise.
  3. Classes with pointer members often cannot safely be copied byte-wise, and certainly not to another machine over the network.
  4. Even if the class is POD, if you send it over the network then you have to make sure that the class has the same layout on the two machines. This is not necessarily the case if the code was compiled with different options, by different compilers, or for different architectures, but sometimes it is the case.

Upvotes: 0

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20739

Where does the magic 0x04 and 4 come from?

If this works, it is just because of a particular alignment and implementation. Better a more structured way:

class testclass
{
    int test,test2;

public:
    void set(int a, int b) {test = a, test2 = b;}
    void echo() {cout << test << " " << test2 << endl;}

    void backup_to(testclass& s)
    { s.test2 = test2; }

    bool has_changed_respect(const testclass& s)
    { return s.test2 == test2; }

    friend std::ostream& operator<<(std::ostream& s, const testclass& a)
    { return s << "testclass["<<&a<<"]: test="<<a.test<<", test2="<<a.test2<< std::endl; }
};

int main()
{
    testclass t1, t2;
    t1.set(1,1);
    t2.set(3,4);

    std::cout << t1 << t2;

    t1.backup_to(t2);
    std::cout << t2;

    t1.set(5,6);
    cout << t1 << t2 << t1.is_changed_respect(t2) << std::endl; 

    return 0;
}

Upvotes: 0

spraff
spraff

Reputation: 33415

Basically, you can't muck around with pointers like that. You generally can't rely on the compiler to coincidentally put meaningful data there.

If you want the address of members you should write &(test1.test2) not &test1+0x04 because even IF an int is 4 bytes and IF the compiler hasn't padded the structure and IF you or someone else hasn't changed the contents of the class, then &test1+0x04 really means "&test1 plus 4*sizeof(test) bytes", it's another way of reaching (&test1)[4] in terms of pointer-array-equivalence.

Also, you can't memcpy over classes in general and expect meaningful results, unless they are POD.

If you want to compare instances of a class, you should write a function which compares each of the members in turn.

You can't write a general-purpose method for this because C++ is not a reflective language. That means you can't write code which magically knows the names and types of the members of a class.

So, if you want to compare and patch data like this, you will need to do something like this:

struct Foo {
    int a;
    int b;

    void export_differences (std :: ostream & o, const Foo & f) {
        if (a != f.a) o << "a " << f.a << " ";
        if (b != f.b) o << "b " << f.b << " ";
        o << ";";
    }

    void import_differences (std :: istream & i) {
        std :: string s;
        while (i >> s) {
            if (s == "a") i >> a;
            else if (s == "b") i >> b;
            else if (s == ";") break;
            else throw std :: runtime_error ("bad input");
        }
    }
};

You will have to write something like this for each class you want to patch.

Upvotes: 2

Related Questions