0

I've been toying around with storing data that will be operated on often into a large array of unsigned char(since C++ does not have a byte type). So, if I store a float into the char array, it will take up the 4 unsigned chars. Simple, except now if I want to edit that data while it is in the array, I would need to access all 4 elements at the same time, which is impossible as far as I know. So, is there a way to edit the 4 unsigned chars into the float value that I desire without resorting to a memcpy()?

Example:

#include <iostream>
#include <string.h>
#include <stdint.h>

using namespace std;

struct testEntity {

    float density;
    uint16_t xLoc, yLoc;
    uint16_t xVel, yVel;
    uint16_t xForce, yForce;
    uint16_t mass;
    uint8_t UId;
    uint8_t damage; 
    uint8_t damageMultiplier;
    uint8_t health;
    uint8_t damageTaken;
};


int main()
{

    testEntity goblin { 1.0, 4, 5, 5, 0, 0, 0, 10, 1, 2, 1, 10, 0 };
    testEntity goblin2;
    unsigned char datastream[24];
    unsigned char* dataPointer = datastream;

    memcpy(&datastream, &goblin, sizeof(testEntity));


    //I know that datastream[0..3] contains information on goblin.density
    //How would I edit goblin.density without memcpy into another testEntity structure?

    memcpy(&goblin2, &datastream, sizeof(testEntity));

return 0;
}
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Daniel Martin
  • 570
  • 1
  • 9
  • 18

3 Answers3

1

Here's what I did:

#include <iostream>
#include <string.h>
#include <stdint.h>

using namespace std;

struct testEntity {

    float density;
    uint16_t xLoc, yLoc;
    uint16_t xVel, yVel;
    uint16_t xForce, yForce;
    uint16_t mass;
    uint8_t UId;
    uint8_t damage; 
    uint8_t damageMultiplier;
    uint8_t health;
    uint8_t damageTaken;
};


int main()
{

    testEntity goblin = { 1.0, 4, 5, 5, 0, 0, 0, 10, 1, 2, 1, 10, 0 };
    testEntity goblin2;
    unsigned char datastream[24];
    unsigned char* dataPointer = datastream;
    testEntity *goblinP;

    memcpy(datastream, &goblin, sizeof(testEntity));


    goblinP = (testEntity *) datastream;

    cout << goblinP->density << endl;

return 0;
}
user2233706
  • 6,148
  • 5
  • 44
  • 86
  • Now what if I doubled the size of datastream so it is now 48 bytes, how would I insert a second testEntity into it? I was thinking memcpy(datastream+sizeof(testEntity), &goblin2, sizeof(testEntity)), and then asigneing goblinP =(testEntity *) dataStream[24], but that seems to fail. Any ideas ? – Daniel Martin Oct 01 '14 at 20:03
  • You need to do `goblinP = (testEntity *) (datastream + 24)` or `goblinP = (testEntity *) &datastream[24]`. Your expression casts the first byte of `dataStream` to an address, which isn't a valid address. – user2233706 Oct 01 '14 at 20:11
  • Why not `goblinP = 1+(testEntity*)datastream`? Anyway, avoid magic numbers. – Deduplicator Oct 01 '14 at 20:14
  • @Deduplicator Or `goblinP[1]` after doing `goblinP = (testEntity *) datastream` – user2233706 Oct 01 '14 at 20:16
  • You could improve this code by writing `unsigned char datastream[sizeof goblin];` ... `memcpy(datastream, &goblin, sizeof goblin);` – M.M Oct 01 '14 at 23:10
  • `goblinP = (testEntity *)datastream;` may cause undefined behaviour due to alignment concerns. Also it violates strict aliasing. – M.M Oct 01 '14 at 23:11
0

by using a union you can access a memory location in various ways, maybe that is what you are looking for (if i understood you correctly):

typedef union 
{
  struct testEntity 
  {
    float density;
    uint16_t xLoc, yLoc;
    uint16_t xVel, yVel;
    uint16_t xForce, yForce;
    uint16_t mass;
    uint8_t UId;
    uint8_t damage; 
    uint8_t damageMultiplier;
    uint8_t health;
    uint8_t damageTaken;
  } te;
  char datastream[24];
} myunion;


...
myunion goblin = { 1.0, 4, 5, 5, 0, 0, 0, 10, 1, 2, 1, 10, 0 }; 

char* goblinP = goblin.datastream;

or e.g. goblin.te.health

EDIT:

It would be better to create a serialize/deserialize function for your struct in order to convert it from/to a struct.

e.g.

ostream& operator<<(ostream& o, const testEntity &in)
{
  typedef union { float f; char bytes[4]; } floatunion;
  floatunion fu = in.density;
  o.write( fu.bytes, 4 );
  ...
  return o;  
}
AndersK
  • 35,813
  • 6
  • 60
  • 86
  • I believe this would be a step in the right direction. I totally forgot about unions! In reality, the datastream(goblinP in your example) would be able to hole hundreds of entities. unsigned char datastream[48] for example, to hold 2 instances of the union. I could memcpy myunion.datastream into the right spots, but how would reinterpret the goblinP data at some interval*24 into the unions datastream? Just myunion.datastream = &goblinP[24/48/72...n*24]? – Daniel Martin Oct 01 '14 at 20:33
  • In C++ it [causes undefined behaviour](http://stackoverflow.com/questions/11373203/accessing-inactive-union-member-undefined) to access a member of a union that was not the last one assigned. – M.M Oct 01 '14 at 23:13
  • @DanielMartin i missed the c++ tag, i think in c++ it would be better to do it differently e.g. create a << >> overloads for your struct and then write/read using them because I guess that is what you ultimately are trying to achieve, isn't it? – AndersK Oct 02 '14 at 05:35
  • @Claptrap Sort of, yeah. The end goal is to have a chunk of contiguous memory that various entities would exist in, with a separate array keeping track of where each entity began and ended. – Daniel Martin Oct 02 '14 at 06:10
  • @DanielMartin without knowing exactly your requirements/constraints it sounds like some STL containers would be better to use than fiddling too much with bytes. A vector of structs ensures they are contiguous in memory – AndersK Oct 02 '14 at 06:15
  • @Claptrap Indeed, however not all the structs will have the same data. For example, some structs would be missing the health field. http://t-machine.org/index.php/2014/03/08/data-structures-for-entity-systems-contiguous-memory/ I'm bad at explaining, basically this article – Daniel Martin Oct 02 '14 at 06:20
0

Your plan of:

goblinP = (testEntity *)datastream;

violates the strict aliasing rule. Paraphrased , the rule is that an object may only be accessed via an expression with that object's type; with a few exceptions. You can access the object as unsigned chars, but you cannot access unsigned chars as the object.

This code might seem to work but you are really playing with fire, as the compiler may decide to optimize out your attempt to read an object, because the standard says that you are effectively reading an uninitialized variable.

To do what you want, declare your buffer line this:

testEntity goblin;

and then you can alias it as bytes by doing:

unsigned char *datastream = reinterpret_cast<unsigned char *>(&goblin);

You can write bytes through datastream and then access goblin to see what you got out. (Of course, this is still subject to the bytes you write in actually being a valid object representation for a testEntity).

M.M
  • 138,810
  • 21
  • 208
  • 365