2

I have two int16_t integers that I want to store in one one 32-bit int.

I can use the lower and upper bits to do this as answered in this question.

The top answers demonstrates this

  int int1 = 345;
  int int2 = 2342;
  take2Integers( int1 | (int2 << 16) );

But my value could also be negative, so will << 16 cause undefined behavior?

If so, what is the solution to storing and retrieving those two numbers which can also be negative in a safe way?


Further context

The whole 32 bit number will be stored in an array with other numbers similar to it.

These numbers will never be utilized as a whole, I'll only want to work with one of the integers packed inside at once.

So what is required is two functions which ensure safe storing and retrieval of two int16_t values into one 4 byte integer

int pack(int16_t lower, int16_t upper);

int16_t get_lower(int);
int16_t get_upper(int);

2 Answers2

3

Here’s the solution with a single std::memcpy from the comments:

std::int32_t pack(std::int16_t l, std::int16_t h) {
    std::int16_t arr[2] = {l, h};
    std::int32_t result;
    std::memcpy(&result, arr, sizeof result);
    return result;
}

Any compiler worth its salt won’t emit code for the temporary array. Case in point, the resulting assembly code on GCC with -O2 looks like this:

pack(short, short):
        sal     esi, 16
        movzx   eax, di
        or      eax, esi
        ret

Without checking, I’m confident that clang/ICC/MSVC produce something similar.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
1

C++ (pre-20) makes it notoriously hard to alias values. If you want to remain within defined behavior pre-20, you'd have to use memcpy.

Like following:

int32_t pack(int16_t l, int16_t h) {
    int32_t r;
    memcpy(&r, &l, 2);
    memcpy(reinterpret_cast<char*>(&r) + 2, &h, 2);
    
    return r;
}

Unpack is similar, with reverse order of memcpy arguments.

Note, that this code is very-well optimized with both Clang and gcc in my experiments. Here is what was produced by latest version of Clang:

pack(short, short):                              # @pack(short, short)
        movzwl  %di, %eax
        shll    $16, %esi
        orl     %esi, %eax
        retq

It all ended up being the same combination of shifts and conjunctions.

SergeyA
  • 61,605
  • 5
  • 78
  • 137