1

I am trying to create a 48-bit integer value. I understand it may be possible to use a char array or struct, but I want to be able to do bit masking/manipulation and I'm not sure how that can be done.

Currently the program uses a 16-bit uint and I need to change it to 48. It is a bytecode interpreter and I want to expand the memory addressing to 4GB. I could just use 64-bit, but that would waste a lot of space.

Here is a sample of the code:

unsigned int program[] = { 0x1064, 0x11C8, 0x2201, 0x0000 };

void decode( )
{
  instrNum = (program[i] & 0xF000) >> 12; //the instruction
  reg1     = (program[i] & 0xF00 ) >>  8; //registers
  reg2     = (program[i] & 0xF0  ) >>  4;
  reg3     = (program[i] & 0xF   );
  imm      = (program[i] & 0xFF  ); //pointer to data
}

full program: http://en.wikibooks.org/wiki/Creating_a_Virtual_Machine/Register_VM_in_C

Abhineet
  • 5,320
  • 1
  • 25
  • 43
Synaps3
  • 1,597
  • 2
  • 15
  • 23
  • Isn't 32-bit sufficiently large for 4GB? – chux - Reinstate Monica Oct 04 '14 at 22:54
  • This is certainly possible, but is it such a big deal having those 16 unused bits if you went with the (simpler, more efficient) 64-bit option? – 500 - Internal Server Error Oct 04 '14 at 22:55
  • 1
    waste lot of space :D? what machine are you going to run this on? 48 = 16*3, so you can use a bit set: array of 3 16-bit integers (or 6 8-bit integers). – mrk Oct 04 '14 at 22:55
  • 1
    @chux: No because it holds the bytecode instruction as well as the memory location. – Synaps3 Oct 04 '14 at 22:55
  • 2
    Suggest using 64-bit `int`, but create special code to efficiently store an array of them - dropping the 4th byte. BTW: I use `int24_t` and `int48_t` with embedded processors all the time. – chux - Reinstate Monica Oct 04 '14 at 23:01
  • @Synaps3 how about the bit fields? – 4pie0 Oct 04 '14 at 23:03
  • Have you profiled? You'll want values you're operating on to be machine words anyway, so the only place it makes a difference is potentially storing packed values in memory. But then you have to way that against the cost of unaligned accesses and byte shifting. – Antimony Oct 04 '14 at 23:18

2 Answers2

2

You can use the bit fields which are often used to represent integral types of known, fixed bit-width. A well-known usage of bit-fields is to represent a set of bits, and/or series of bits, known as flags. You can apply bit operations on them.

#include <stdio.h>
#include <stdint.h>

struct uint48 {
    uint64_t x:48;
} __attribute__((packed));
4pie0
  • 29,204
  • 9
  • 82
  • 118
  • Note: 1) "A bit-field shall have a type that is a qualified or unqualified version of `_Bool`, `signed int`, `unsigned int`, or some other implementation-defined type." C11dr §6.7.2.1 5. What this answer proposes is implementation-defined behavior. 2) `printf( "%zu\n", sizeof(ui48));` – chux - Reinstate Monica Oct 05 '14 at 04:31
  • @chux correct, I have changed the type to uint64_t. What about your point 2) ? http://ideone.com/1nVesR – 4pie0 Oct 05 '14 at 12:03
  • 1) Changing to `uint64_t` is no different than `unsigned long long` concerning meeting C11dr §6.7.2.1 5. 2) `sizeof()` returns type `size_t`, not `unsigned`. These 2 types _may_ differ. http://stackoverflow.com/questions/940087/whats-the-correct-way-to-use-printf-to-print-a-size-t/940119 – chux - Reinstate Monica Oct 05 '14 at 14:43
  • @chux So this is implementation defined, OK. This is still valid either. – 4pie0 Oct 05 '14 at 14:54
  • 1) is implementation-defined. As I see it, the compiler will either work with it as desired or complain at compile time. 2) is UB. "If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined." §7.21.6.1 9 Code will compile, but the run-time results could be way off. 3) Both not major issues, as OP _should_ be able to cope. Just wanted to point these out for your review. – chux - Reinstate Monica Oct 05 '14 at 15:09
1

Use a structure or uint16_t array with special functions for an array of uint48.

For individual instances, use uint64_t or unsigned long long. uint64_t will work fine for individually int48, but may want to mask off the results operations like * or << to keep upper bits cleared. Just some space saving routines are needed for arrays.

typedef uint64_t uint48;
const uint48 uint48mask = 0xFFFFFFFFFFFFFFFFull; 

uint48 uint48_get(const uint48 *a48, size_t index) {
  const uint16_t *a16 = (const uint16_t *) a48;
  index *= 3;
  return a16[index] | (uint32_t) a16[index + 1] << 16
          | (uint64_t) a16[index + 2] << 32;
}

void uint48_set(uint48 *a48, size_t index, uint48 value) {
  uint16_t *a16 = (uint16_t *) a48;
  index *= 3;
  a16[index] = (uint16_t) value;
  a16[++index] = (uint16_t) (value >> 16);
  a16[++index] = (uint16_t) (value >> 32);
}

uint48 *uint48_new(size_t n) {
  size_t size = n * 3 * sizeof(uint16_t);
  // Insure size allocated is a multiple of `sizeof(uint64_t)`
  // Not fully certain this is needed - but doesn't hurt.
  if (size % sizeof(uint64_t)) {
    size += sizeof(uint64_t) - size % sizeof(uint64_t);
  }
  return malloc(size);
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256