views:

252

answers:

5

I'm having an issue where using vector.push_back(value) is overwriting the final value, rather than appending to the end. Why might this happen? I have a sample item in the vector, so it's size never hits zero. Below is the code..

void UpdateTable(vector<MyStruct> *Individuals, MyStruct entry)
{
    MyStruct someEntry;
    bool isNewEntry = true;

    for (int i = 0; i < Individuals->size(); i++)
    {
        if (!(strcmp(Individuals->at(i).sourceAddress, entry.sourceAddress)))
        {
            isNewEntry = false;
            //snip.  some work done here.
        }
    }

    if(isNewEntry)
    {
        Individuals->push_back(entry);
    }
}

This let's my first "sample" value stay in, and will allow for just one more item in the vector. When 2 new entries are added, the second overwrites the first, so the size is never larger than 2.

edit: More code, since this is apparently not the issue?

void *TableManagement(void *arg)
{
      //NDP table to store discovered devices.
      //Filled with a row of sample data.
      vector<MyStruct> discoveryTable;
      MyStruct sample;
      sample.sourceAddress = "Sample";
      sample.lastSeen = -1;
      sample.beaconReceived = 1;
      discoveryTable.push_back(sample);

      srand(time(NULL));
      while(1)
      {
          int sleepTime = rand() % 3;
          sleep(sleepTime);
          MyStruct newDiscovery = ReceivedValue();
          if (newDiscovery.lastSeen != -1000) //no new value from receivedValue()
          {
              UpdateTable(&discoveryTable, newDiscovery);
          }
          printTable(&discoveryTable);
      }
      return NULL;
}
A: 

Your code looks alright to me. Is it possible that you are not passing the right vector in? What I mean is that the behaviour you describe would appear if somehow your Individuals vector is being reset to its orginal 1-entry state before you tried to add the 3rd entry, then it would appear as if your 2nd entry was being overwritten.

Here is what I mean:

int test_table()
{
  string SampleAddresses[] = {"Sample Address 1", "Sample Address 2"};

  for (int i = 0; i < 2; i++)
  {
     // All this work to build the table *should* be done outside the loop; but we've accidentally put it inside
     // So the 2nd time around we will destroy all the work we did the 1st time
     vector<MyStruct> Individuals;
     MyStruct Sample;
     Sample.sourceAddress = "Sample Address 0";
     Test.push_back(Sample);

     // this is all we meant to have in the loop
     MyStruct NewEntry;
     NewEntry.sourceAddress = SampleAddresses[i];
     UpdateTable(Individuals, NewEntry);
  }

  //Now the table has 2 entries - Sample Address 0 and Sample Address 2.
}

If this was all your code then the problem would be obvious. But it might be concealed in some other pieces of code.

Tom Smith
A: 

Seems odd to me: Maybe there is something wrong with the //snip part of the code?

Try to log the size of the vector before and after the push_back call (either in the debugger or using cout) and also have a look at the isNewEntry variable.

MartinStettner
there is nothing of importance in the //snip part. I have appended the calling method, however.
Mark
+1  A: 

The scope for the items you are pushing into the database is expiring. They're being destructed when you leave the {} in which they were created - and as such the reference to them is no longer valid.

You need to change it from vector<MyStruct> to vector<MyStruct*> (preferably using safe pointers from Boost:: instead of pointers, but you get the idea).

You are creating the item within the (limited) scope and pushing it onto the vector (while the struct is copied, the strings in it are not!) it then reuses the same memory location (most likely if properly optimized) to store the next "new" struct and the one after that and so on and so forth.

Instead, within the limited scope create MyStruct *myObject = new MyStruct and assign its values, then push the pointer to the vector.

Remember to delete all values from the vector before clearing it/destroying it!!

Or, of course, you could use std::string/CString/whatever instead of a char array and avoid the issue entirely by having a safe-to-copy struct.

Computer Guru
It took me a while to get this, so here is a comment to clarify: you're storing the address as a character array, which means the only thing the struct actually knows is the memory address of (ie pointer to) the array. When the loop comes round, the array has passed out of scope and so the pointer (held in the vector) is not valid. Coincidentally the same piece of memory gets reused for the Address field of the new struct. So the old pointer becomes coincidentallyvalid again, this time pointing to the new Address; which is why the IsNewEntry test fails and the new struct doesn't get added.
Tom Smith
@Computer If you push an item into a vector, a copy is made. There are no scope issues such as you describe.
anon
But what is getting copied is a char* - not the entire char array. And then the array pointed to by the pointer goes out of scope.
Tom Smith
@Tom That would also be true if the vector used pointers. And as the OP hasn't posted his struct definition, and may be using arrays ratherv than pointers, we can't say if the character pointer thing is the problem. But if it is, this answer does NOT address it.
anon
Yes, I suppose. It's sort-of the right problem (assuming the character pointers are the problem) but not the right solution. Adam Bowen's answer is the same but better.
Tom Smith
Neil, you're right. But from experience when I first started C++ coding and passing STL objects around years ago, my issue with the last item "overwriting" earlier items was due to the shallow-copy behavior and due to scope expiration.
Computer Guru
Maybe. Since we can't see MyStruct is handling the sourceAddress field, we can't know.This response is wrong in that the structs that are pushed onto the vector are /copies/ of the local struct. Changing the vector to use MyStruct pointers would make the problem worse, because then the vector would hold pointers to MyStruct's whose scope is expired, and which therefore have been destroyed. If the original is deleting the sourceAddress char array in it's destructor then the copies hold pointers to freed char arrays, and all bets are off.
Tim Schaeffer
Don't forget that storing pointers in STL containers breaks standard algorithms with remove semantics.
Billy ONeal
@BillyO'Neal No, it does not. Storing pointers to dynamic allocated objects might cause a leak, but there is no semantic problem.
anon
Yes, there is. STL algorithms which have remove semantics overwrite or destroy the objects in STL containers. If that object is the pointer, then the programmer has absolutely no way of reclaiming the memory referenced by those pointers. See Scott Meyers' Effective STL Item # 33: Be wary of remove-like algorithms on containers of pointers.
Billy ONeal
+1  A: 

ComputerGuru's answer works however there in another alternative. You can create a copy constructor and overload operator= for MyStruct. In these operations, you need to copy the actual string into the new struct. In C++, structs are nothing more than classes with default public access instead of default private access. Another alternative is to use std::string instead of char* for the string value. C++ strings already have this behavior.

struct MyStruct {
   std::string sourceAddress;
   int lastSeen;
   int beaconReceived;
};
+3  A: 

I'm going to hazard a guess:

Suppose MyStruct is declared like

struct MyStruct
{
    const char *sourceAddress;
    // Other Gubbins ...
};

And that ReceivedValue does something like

MyStruct ReceivedValue()
{
    static char nameBuffer[MAX_NAME_LEN];

    // Do some work to get the value, put the name in the buffer

    MyStruct s;
    s.sourceAddress = nameBuffer;
    // Fill out the rest of MyStruct
    return s;
}

Now, every structure you push into your vector has sourceAddress pointing to the same global buffer, every time you call ReceivedValue it overwrites that buffer with the new string - so every entry in your vector ends up with the same string.

I can't be sure without seeing the rest of your code, but I can be sure that if you follow some of the good C++ style suggestions in the comments to your question this possiblity would go away.

Edit for clarification: there's no need to heap allocate your structures, simply declaring sourceAddress as a std::string would be sufficient to eliminate this possibility.

Adam Bowen
Simply declaring it as an array of char would fix it too, and allow him to retain his existing code.
anon
Looking at it some more, the assignment of "Sample" to sourceAddress indicates that sourceAddress must be declared as a const char* and the strcmp that it can't be a std::string. That would make this a likely candidate for the problem, so long as "Sample" isn't being overwritten in the array (which I don't think I could explain with this answer).
Adam Bowen