2

I am using a map type to mimic a memory i.e.,

   map<long, uint8_t> memory

Where the long is the address and the uint8_t the byte of memory.

Now I can write (signed) long types out without a problem (I think!):

void Memory::writeLong(const long address, const long value)
{
    if (address < start || address + sizeof(long) > start + memorySize) {
        cout << "Memory::readLong out of range" << endl;
        throw "Memory class range error";
    }

    uint8_t *valRep = (uint8_t *) &value;
    for (int i = 0; i < sizeof(long); i++)
    {
        contents[address + i] = *((uint8_t *)(valRep + i));
    }
}

But I cannot get reads to correctly handle the sign of the longs. Here is the read code:

long Memory::readLong(const long address)
{
    long retVal = 0;

    if (address < start || address + sizeof(long) > start + memorySize) {
        cout << "Memory::readLong out of range" << endl;
        throw "Memory class range error";
    }

    uint8_t in[8];
    for (int i = 0; i < sizeof(long); i++)
    {   
        try {

            in[i] =(uint8_t) contents.at(address + i) << (i * 8);
        }
        catch (const out_of_range& err)
        {
            contents[address] = 0;
        }
    }
    memcpy(&retVal, in, 8);
    return retVal;
}

But this gives me bad results when trying to read negative numbers e.g.: (in the form written: read)

-4:1095216660732,-8:1095216660728,-5:1095216660731,-1:1095216660735,-1224:1095216660536

Though it seems to read positive numbers correctly. This, it seems is an issue with the 2's complement representation, but I cannot quite put my finger on what is going wrong. Could someone tell me what I have messed up here?

Thanks

adrianmcmenamin
  • 1,081
  • 1
  • 15
  • 44
  • Rather than using `long`s for addresses consider `size_t`. It is a better approximation of memory indexing. – user4581301 Aug 25 '15 at 23:10
  • Also kind of interested that you get anything over 255. `<< (i * 8);` shifts all but the first byte out of the byte. – user4581301 Aug 25 '15 at 23:23
  • If you are operating with a 32 bit compiler, the results of this `memcpy(&retVal, in, 8);` will also be interesting. – user4581301 Aug 25 '15 at 23:26
  • what is `contents` ? Where is your map `memory` used? It would be better if you can post a [MCVE](http://stackoverflow.com/help/mcve) – M.M Aug 25 '15 at 23:35
  • @user4581301 also would be interesting on a 64-bit compiler with 32-bit `long` – M.M Aug 25 '15 at 23:36
  • @MattMcNabb right. Forgot that was a possibility. Looks like I won't have to reinstall mingw after all. – user4581301 Aug 25 '15 at 23:42
  • Don't do the shift in: `in[i] =(uint8_t) contents.at(address + i) << (i * 8);` -- you are already doing bytewise addressing with the array index. – Christopher Oicles Aug 26 '15 at 00:04

1 Answers1

3

Nothing seriously wrong in here. One unnecessary cast, but otherwise looks good.

void Memory::writeLong(const long address, const long value)
{
    if (address < start || address + sizeof(long) > start + memorySize) {
        cout << "Memory::readLong out of range" << endl;
        throw "Memory class range error";
    }

    uint8_t *valRep = (uint8_t *) &value;
    for (int i = 0; i < sizeof(long); i++)
    {
        contents[address + i] = *((uint8_t *)(valRep + i));// don't need cast
    }
}

Couple bits of nastiness in the read:

long Memory::readLong(const long address)
{
    long retVal = 0;

    if (address < start || address + sizeof(long) > start + memorySize) {
        cout << "Memory::readLong out of range" << endl;
        throw "Memory class range error";
    }

    uint8_t in[8]; // should be uint8_t in[sizeof(retVal)];
                   // why sizeof(retVal) and not sizeof(long)
                   // someone could cut and paste to make the different typed 
                   // versions and change only retVal. Might as well lock them
    for (int i = 0; i < sizeof(long); i++) //nag nag, signed unsigned mismatch
    {   
        try {

            in[i] =(uint8_t) contents.at(address + i) << (i * 8);
            // in[i] is 8 bits. << (i * 8) ensures all but the first byte are shifted 
            // out of byte range
        }
        catch (const out_of_range& err)
        {
            contents[address] = 0;
        }
    }
    memcpy(&retVal, in, 8); // overruns retVal if retval is 32 bits. Use 
                            // sizeof(retVal) again.
    return retVal;
}

So this can't possibly work or generate the results OP reported. My guess is OP had something that almost worked and then started flailing around trying to fix it and made things worse.

How I'd do it:

Edit: Turns out type punning with unions is illegal. Sucks, because I think it's safer and more easily read than the pointer version. Probably the only reason the pointer version is not illegal is the huge volume of legitimate cases for casting to byte.

I also moved the try/catch block outside the reassembly loop because once you read outside memory, the recovery's pretty much toast.

#include <iostream>
#include <map>

std::map<long, uint8_t> contents;
void writeLong(const size_t address, const long value)
{
/* not needed for test case
    if (address < start || address + sizeof(long) > start + memorySize) {
        cout << "Memory::readLong out of range" << endl;
        throw "Memory class range error";
    }
*/
    uint8_t *valRep = (uint8_t *) &value;
    for (size_t i = 0; i < sizeof(long); i++)
    {
        contents[address + i] = *(valRep + i);
    }
}
long readLong(const size_t address)
{
// Verbotten!
//    union
//    {
//        long val;
//        uint8_t arr[sizeof(val)];
//    }retVal; // Ahhhrrrrrr! Here be endian nightmare!

    long retVal;
    uint8_t *valRep = (uint8_t *) &retVal; 
    // uglier, but legal. Consider using the no-punning version below

/* not needed for test case
    if (address < start || address + sizeof(long) > start + memorySize) {
        cout << "Memory::readLong out of range" << endl;
        throw "Memory class range error";
    }
*/
 //   uint8_t in[sizeof(long)]; replaced by evil union abuse above
    try 
    {
        for (size_t i = 0; i < sizeof(long); i++)
        {
    // Not legal
    //        retVal.arr[i] = contents.at(address + i) ;
            *(valRep + i) = contents.at(address + i);
        }
    }
    catch (const std::out_of_range& err)
    {
        retVal = 0;
    }

//    memcpy(&retVal, in, sizeof(long)); replaced by evil union abuse above
// Not legal
//    return retVal.val;
    return retVal;
}

void test(long val)
{
    writeLong(0, val);
    std::cout << "In " << val << " out " <<  readLong(0) << std::endl;
}

int main()
{
    test(-4);
    test(-8);
    test(-5);
    test(-1);
    test(-1224);
    test(0);
    test(1);
    test(0x7fffffff);
    test(0x80000000);
}

Output:

In -4 out -4
In -8 out -8
In -5 out -5
In -1 out -1
In -1224 out -1224
In 0 out 0
In 1 out 1
In 2147483647 out 2147483647
In -2147483648 out -2147483648

I will miss my friend the type punning union, but there are punning-free versions.

void writeLong(size_t address, long value)
{
// not needed for test case
//    if (address < start || address + sizeof(long) > start + memorySize) {
//        cout << "Memory::readLong out of range" << endl;
//        throw "Memory class range error";
//    }

    for (size_t i = 0; i < sizeof(long); i++)
    { // note this only preserves the proper memory layout for little endian systems
        contents[address + i] = (uint8_t)(value &0xFF);
        value >>= 8;
    }
}
long readLong(size_t address)
{
    long retVal;

// not needed for test case
//    if (address < start || address + sizeof(long) > start + memorySize) {
//        cout << "Memory::readLong out of range" << endl;
//        throw "Memory class range error";
//    }
    try
    {
        address += sizeof(long) - 1;
        retVal = contents.at(address--);
        for (size_t i = 0; i < sizeof(long) - 1; i++)
        { // this only works if the little endian memory layout is preserved
            retVal <<= 8;
            retVal += contents.at(address--);
        }
    }
    catch (const std::out_of_range& err)
    {
        retVal = 0;
    }
    return retVal;
}
user4581301
  • 33,082
  • 7
  • 33
  • 54
  • 1
    Union aliasing isn't permitted in ISO C++. Some common compilers offer it as an extension. Instead you could use `memcpy`. – M.M Aug 26 '15 at 04:32
  • Thanks, you were right that the code posted and the output didn't match because of editing after the output. – adrianmcmenamin Aug 26 '15 at 16:47
  • @adrianmcmenamin removed union and replaced with pointer to make the example compliant, but only sort of compliant. Added example of how to do it without any type punning tricks. – user4581301 Aug 26 '15 at 19:30