-1

I need to combine four numbers in hex format into single number. The first option that I thought of was to do left shift by n*16 (n=0,1,2,3..) for each number. This works fine when the numbers are 0xABCD.

If a number is 0x000A, the leading zeroes are ignored and whole thing stops working (not performs as expected). I need to have all the leading zeroes because I have to know the position of 1's in the 64bit number.

user.profiles is a 64bit value where the each part of tmp_arr is shifted to the left and stored. Am I missing something here? Or am I just going crazy?

for(int i = 0; i < 4; i++)
{
    EE_ReadVariable(EE_PROFILES_1 + i,  &tmp_arr[i]);   // increment the address by i (D1->D2->D3->D4)

    user.profiles |= (tmp_arr[i] << (i*16));    // shift the value by multiples of 16 to get 64bit number
}
KristjanB
  • 11
  • 1
  • 4
    The types of all variables involved is extremely important, so please post complete code with variable declarations. Also, what system is this? Is this for some restricted microcontroller system with 16 bit `int`? – Lundin Jun 29 '21 at 09:16
  • 1
    Integers doesn't store leading zeros. No integer store that. To get leading zeros in output you have to format the output using the correct formatting specifier. – Some programmer dude Jun 29 '21 at 09:16
  • `uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data)` returns `0` if variable was found, `1` if the variable was not found and `NO_VALID_PAGE` if no valid page was found. Don't forget to check the return value. – Ted Lyngmo Jun 29 '21 at 09:19
  • @Lundin tmp_arr is a 16bit number array. It's for STM32 microcontroller which seems to support 64bit numbers. – KristjanB Jun 29 '21 at 09:22
  • 1
    I assume `tmp_arr` is declared as `uint16_t tmp_arr[4];`? If so, you probably want `((uint64_t)tmp_arr[i]) << (i*16)` - why do you need an array of temporaries though? – Ted Lyngmo Jun 29 '21 at 09:22
  • @Someprogrammerdude I'm aware of that yes. But is there any way to format the output without using strings? – KristjanB Jun 29 '21 at 09:24
  • @TedLyngmo Yes, minute after posting I realized that and also that I don't need the array so I replaced it with a single variable. Also, I added type cast and now it works better. However, there is still a problem with leading zeroes. For example: first 3 numbers are added correctly but if the last number has any leading zeroes they are removed; 0xaaa002000040040 where far left 16 bit number should be 0aaa. – KristjanB Jun 29 '21 at 09:27
  • 2
    @KristjanB Please include the code where you can see that the leading zeroes are missing. – Ted Lyngmo Jun 29 '21 at 09:28
  • @TedLyngmo user.profiles has value 0xaaa002000040040 when it leaves the loop. The values that are read from flash are: 0x0040 0x0004 0x0020 0x0AAA respectively. – KristjanB Jun 29 '21 at 09:31
  • 2
    Please don't try to explain your code in comments. **[Edit] your question and show your code and input/output values.** – user694733 Jun 29 '21 at 09:33
  • 2
    @KristjanB Is [`printf("%016" PRIx64 "\n", x);`](https://godbolt.org/z/x8nnox8eW) what you're after? – Ted Lyngmo Jun 29 '21 at 09:41
  • Are you actually using `tmp_arr`? If not, could it not simply be a single `uint16_t` reused? – Clifford Jun 29 '21 at 12:32
  • 2
    @KristjanB : Generally if it is pointed out that your question is deficient in some way, the response should be to edit the question rather then add information in comments. Information about data types of both `user.profiles` and `tmp_arr` is critical (and was requested). You say "16bit number array", but that is not a type name, but a description of a type - and an ambiguous one since it is not stated if it is signed it unsigned. "Leading zeros" are a matter of _presentation_ not _representation_ they are usually _implicit_ unless you choose to explicitly _present_ them when displaying. – Clifford Jun 29 '21 at 12:59
  • If I buy two hamburgers, I don't write "02 hamburgers". Still, "2" and "02" are the same number, only their representation is different. – linuxfan says Reinstate Monica Jun 30 '21 at 07:13

4 Answers4

1

C allows type-punning using unions, so you could have something like this:

union value_union
{
    uint16_t v16[4];
    uint64_t v64;
};

// ...

union value_union values;
values.v16[0] = value1;
values.v16[1] = value2;
values.v16[2] = value3;
values.v16[3] = value4;

printf("64-bit value = 0x%016"PRIx64"\n", values.v64);
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
1

In order to write embedded C, it is very important that you know of Implicit type promotion rules.

tmp_arr[i] << (i*16) on a 32 bit system like STM32 promotes the tmp_arr[i] argument to 32 bit signed int. This comes with two complications:

  • If you happen to shift a value into the sign bit of this 32 bit int, you get an undefined behavior bug.
  • If you shift beyond the size of this 32 bit int, you get an undefined behavior bug (and data shifted out is lost.

You need to use 64 bit unsigned arithmetic for this (which will be fairly inefficient on a 32 bitter):

user.profiles |= (uint64_t)tmp_arr[i] << i*16;  

The size and type of what's on the left side of the assignment operator is completely irrelevant here.

Also, when coding for embedded systems get rid of sloppy int and the other "primitive" default types, use the types of stdint.h only. In the average embedded system, you rarely ever want any signed types, they just create problems. For STM32, you'll want to use uint32_t in most cases.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • That is true C in general, the OP code is an error regardless of being embedded. – Clifford Jun 29 '21 at 12:41
  • @Clifford But embedded systems are much more likely to use bitwise operators everywhere. – Lundin Jun 29 '21 at 12:46
  • Maybe, but there are plenty of situations where this might be true in desktop code too. Communication protocols, binary file formats, device drivers, code that processes data _from_ an embedded system for example. Just wanted to make it clear that the issue was not unique to C on embedded systems. Or for that matter that "embedded C" is not some separate language with different semantics. – Clifford Jun 29 '21 at 12:52
1

As I mentioned in the comments, you need to cast your uint16_t temporary to uint64_t before shifting up and you should check for errors.

The "missing" leading zeroes probably comes from using the wrong format for printf.

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

uint64_t get_profiles() {
    uint64_t rv = 0;
    uint16_t tmp;
    for(int i = 0; i < 4; i++) {
        uint16_t res = EE_ReadVariable(EE_PROFILES_1 + i, &tmp);
        switch(res) {
        case 0: rv |= (uint64_t)tmp << i*16; break;
        case 1: /* variable not found */ break;              // deal with error
        case NO_VALID_PAGE: /* no valid page found */ break; // deal with error
        }
    }
    return rv;
}

int main () {
    // ...
    user.profiles = get_profiles();
    printf("%016" PRIx64 "\n", user.profiles); // prints leading zeroes
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
0

You can avoid casting and the need to force intermediate 64-bit arithmetic by shifting the target and masking into the least significant 16 bits in two separate implicitly 64 bit operations, thus:

user.profiles = 0u ;
for(int i = 0; i < 4; i++)
{
    EE_ReadVariable( EE_PROFILES_1 + i,  &tmp_arr[i] ) ;

    user.profiles <<= 16 ;
    user.profiles |= tmp_arr[i] ;
}

In the shift-assignment and the OR-assignment, the right-hand operand it is implicitly promoted the same type as the right hand.

If tmp_arr is not used then:

user.profiles = 0u ;
for(int i = 0; i < 4; i++)
{
    uint16_t tmp = 0 ;
    EE_ReadVariable( EE_PROFILES_1 + i,  &tmp ) ;

    user.profiles <<= 16 ;
    user.profiles |= tmp ;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165