2

Let's say I have a dword.

uint32_t var1 = 0xABCD1234;

I need to get the upper word as it's own value. There are several different ways to do this in C. Which one is most likely to compile most efficiently on a 32-bit processor/OS? Does it make a difference if it's a 64-bit processor/OS?

1. Shifting

uint16_t var2 = var1 >> 16;

2. Casting

This requires knowing the endianess of the processor, so it's a negative in that regards, but assume you have the endianess correct.

uint16_t var2 = *( (uint16*)&var1 );

or

uint16_t var2 = *( (uint16_t*)&var1 + 1 );

3. Dividing

uint16_t var2 = (uint16_t)( var1 / ( 1ULL << 16 ) );

4. Anything else?

Did I miss any other way to do it?

EDIT: Yes, I missed the union.

union { uint32_t v32; uint16_t v16[2]; } u;

uint32_t var1 = 0xABCD1234;
u.v32 = var1;
uint16_t var2 = u.v16[1]; // or u.v16[0] depending on endianess
JonS
  • 621
  • 7
  • 25
  • 2
    "Shifting" and "dividing" are the same method, i.e. two different ways to express the same thing. By definition, right shift of unsigned value is integer division by a power of 2. The same power of 2 you use in your "dividing" method. – AnT stands with Russia Jun 21 '17 at 00:08
  • 2
    The "most efficient way" ultimately depends on the exact architecture. C does not make any guarantees in that regard. – Siguza Jun 21 '17 at 00:13
  • Sidenote: "DWORD" is no well defined term in programming. Use more clear phrases (`uint32_t` e.g.). And `uint16_t var2 = *( (uint16*)&var1 );` invokes _undefined behaviour_ (look it up if you don't know this - it is a fundamental concept in C).. – too honest for this site Jun 21 '17 at 01:03
  • Note: `LL` not needed in `( 1ULL << 16 )`, `( 1LU << 16 )` or better `((uint32_t)1u << 16 )` or simply `0x10000u`. – chux - Reinstate Monica Jun 21 '17 at 03:00
  • If you want to use a type like 'DWORD', you should add the 'Windows' tag. – ThingyWotsit Jun 21 '17 at 06:54
  • dword absolutely has a clear definition. It's a double word, which is twice the size of a word which is twice the size of a byte. Just because Windows uses that as the name of a type doesn't change the fact that it was already defined before Windows. – JonS Jun 23 '17 at 01:33
  • @JonS it was? I have only ever seen 'DWORD' as an MS type.. – ThingyWotsit Jun 23 '17 at 14:55
  • @ThingyWotsit It's generally used in assembly, which every good C programmer should know. ;) – JonS Jun 25 '17 at 02:00
  • @JonS yes, you're right. It's an Intel thingy, not a Windows thingy. My bad:( – ThingyWotsit Jun 26 '17 at 17:21

4 Answers4

5

I would put my bet on Shifting

Casting would not be a portable solution. Think about endianess

On a side note, I would not recommend casting from a few recent experiences on a custom (in-house) architecture and am sure there should be similar constrains elsewhere

  • Casting an un-aligned uint8 pointer to uint32 pointer led to access violation in the processor
  • Casting a uint32 pointer of a special purpose high speed memory region (mapped to DDR) to a uint8 pointer led to data corruption - it was not meant to be addressed by anything less than a word (4 byte system)

I admit these are architecture dependent constraints but the consequences of an unsafe coding style can be drastic!

Zakir
  • 2,222
  • 21
  • 31
3

If by "upper word" you mean most significant word, then your "shifting"/"dividing" method is the right way to go. Note that they are equivalent, by definition.

Your casting method is invalid, since it violates strict aliasing rules. The proper implementation of this approach would involve a union

union { uint32_t v32; uint16_t v16[2]; } u;

uint32_t var1 = 0xABCD1234;
u.v32 = var1;
uint16_t var2 = u.v16[1];

The result will indeed be platform-dependent (little-endian vs. big-endian). You can also use memcpy for that purpose

uint32_t var1 = 0xABCD1234;
uint16_t var2;
memcpy(&var2, (unsigned char *) &var1 + sizeof(uint16_t), sizeof(uint16_t));

with the same set of portability issues.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
1

Casting breaks anti-aliasing rules, as well as being endian dependent, so is not a contender.

With a good compiler, no reason to expect uint16_t var2 = var1 >> 16; and uint16_t var2 = var1 / 0x10000u; to emit different performance code. If they do, then "Did I miss any other way to do it?" --> get a better compiler.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • If optimizations are disabled, they could be very different. – JonS Jun 23 '17 at 01:45
  • @JonS True they could be different for a number of reasons, yet if the compiler is optimizing for speed, a good compiler would emit the same performance code for both. If optimizations are disabled the whole point of "What is the most efficient way ...?" appears moot. – chux - Reinstate Monica Jun 26 '17 at 23:54
0

Bit shifting is definitely the most efficient way to do this. Computers have specific hardware to do these exact types of operations, so they can be done very quickly. These hardware components are called Arithmetic Logic Units (ALU). You can find a little bit more about ALUs and bit shifting here.

LucasN
  • 243
  • 1
  • 2
  • 14