1

i have an uint8_t Variable which contains a substring of 4 hexadecimal variables. Example:

uint8_t String[10] = "00AABBCC";

I would like to take these 4 hex Variables into different hex values:

uint8_t Data_Byte[4];
Data_Byte[0]=0x00;
Data_Byte[1]=0xAA;
Data_Byte[2]=0xBB;
Data_Byte[3]=0xCC;

How can I take these 4 substrings into 4 different uint8_t Variables?

ramles
  • 19
  • 3
  • 1
    `"00AABBCC"` is not a valid `uint8_t` value. – pmg Jul 23 '21 at 12:58
  • @pmg it actually should be an array. I edited the question. – ramles Jul 23 '21 at 13:01
  • `for (int i = 0; i < 4; i++) { Data_Byte[i] = (hexvalue(String[2*i]) << 4) + hexvalue(String[2*i + 1]); }` where `hexvalue()` converts `'0'` to `0`, ...`'A'` to `10`... – pmg Jul 23 '21 at 13:06
  • 3
    @ramles Do the strings always have even length? – klutt Jul 23 '21 at 13:32
  • So many string functions in the answers. I'm surprised no one mentioned `toupper()`. Or do you know that the letters will be uppercase? – Tim Randall Jul 23 '21 at 13:32

4 Answers4

4

You can use sscanf to parse each two-character pair in the string into a number:

uint8_t arr[strlen(String) / 2];
for (int i = 0; i < strlen(String); i += 2) {
    sscanf(String + i, "%2hhx", &arr[i / 2]);
}

If you're developing on a system with limited sscanf support, you can use something like this:

for (int i = 0; i < strlen(String); i += 2) {
    uint8_t val1 = isdigit(String[i]) ? (String[i] - '0') : (String[i] - 'A' + 10);
    uint8_t val2 = isdigit(String[i + 1]) ? (String[i + 1] - '0') : (String[i + 1] - 'A' + 10);

    arr[i / 2] = val1 << 4 | val2;
}
Daniel Kleinstein
  • 5,262
  • 1
  • 22
  • 39
  • 2
    `scanf` for this simple job? – 0___________ Jul 23 '21 at 13:13
  • 1
    @0___________ Is this a joke :)? Your solution reimplements internal `sscanf` logic of parsing characters (and probably far less efficiently). Surely using standard library functions is much simpler than reimplementing them :) – Daniel Kleinstein Jul 23 '21 at 13:15
  • ```(and probably far less efficiently)``` is it a joke? – 0___________ Jul 23 '21 at 13:18
  • And there is a small problem: your code does not work. https://godbolt.org/z/G4eExPq9f – 0___________ Jul 23 '21 at 13:21
  • 1
    Not a joke - your `convert` function can be made much more efficient. You don't need a `digits` array or calls to `strlen` or `memset` or `strchr` - just get the value based on `chrs[i] >= 'A' ? chrs[i] - 'A' : chrs[i] - '0'` which has the downside of assuming that `'A' >= '0'` - but you can add a separate check for this for full portability. – Daniel Kleinstein Jul 23 '21 at 13:22
  • It does work, the problem in your example is that you've set the `String` variable to be 9-chars long (as opposed to OP's code which is 10-chars long). If you write `uint8_t String[10] = "00AABBCC";` it works fine. – Daniel Kleinstein Jul 23 '21 at 13:24
  • It means that it does not work. Abstracting from the very bad example of programming in the `main` function – 0___________ Jul 23 '21 at 13:25
  • It does work, your example is buggy. If you set the length of string to 9 then you need to change the `for` loop check to `for (int i = 0; i < 8; i += 2) {`. Presumably the OP's string is of even-length, otherwise it wouldn't make sense - but it's very possible to add a separate check to prevent this. – Daniel Kleinstein Jul 23 '21 at 13:27
  • 2
    @0___________ OP should be clearer about this, but honestly, there is no reason to suspect that the intention is that strings of odd length should be valid. – klutt Jul 23 '21 at 13:31
  • @klutt nine chars because of null character (C string), not the length of string. – 0___________ Jul 23 '21 at 13:34
  • @0___________ Hmmm, that's true. Daniel should change `sizeof (String)` to `strlen(String)` – klutt Jul 23 '21 at 13:36
  • @DanielKleinstein your solution is "only" 20 times slower than mine. https://godbolt.org/z/1T6v3aW3r – 0___________ Jul 23 '21 at 13:37
  • @0___________ Interesting, I assumed `sscanf` would be more efficient.. Probably because it needs to parse `"%2hhx"` - I would guess that this is mostly what accounts for the difference. My non-`sscanf` code runs about fifteen times faster than yours, or more than 300 times faster than `sscanf` (!) Still, if I were writing the program I'd opt for the `sscanf` version for readability reasonas if efficiency wasn't a concern. – Daniel Kleinstein Jul 23 '21 at 13:56
  • What if 'A' is not > '0'? – 0___________ Jul 23 '21 at 14:25
  • @DanielKleinstein He has a point there. Why make the code dependent on ascii if you don't need to? And if it is, you should document it. – klutt Jul 23 '21 at 15:04
  • @DanielKleinstein Hmmm, I would probably modify it by using `isdigit` like this: `uint8_t val1 = isdigit(String[i]) ? (String[i] - '0') : (String[i] - 'A' + 10);` It is both clearer and more portable. – klutt Jul 23 '21 at 15:17
  • @klutt Good suggestion, I'll include this :). – Daniel Kleinstein Jul 23 '21 at 15:20
  • 1
    @DanielKleinstein You can remove the documented assumption I did not read now ;) – klutt Jul 23 '21 at 15:22
  • @DanielKleinstein I realized that you were correct that it does not really matter, but it annoyed me with the reduced portability, so immediately I started looking for a way to make it more readable by making it portable. Good diplomacy, right? :D – klutt Jul 23 '21 at 15:27
  • 1
    @klutt Yep :) and you found a great balance between readability and portability – Daniel Kleinstein Jul 23 '21 at 15:30
  • Late to this debate, but: IMO, `sscanf` is a *perfectly fine* approach to this problem. (I've used it myself, and I say this as the world's least fan of the scanf family, in general.) It's especially appropriate for someone who for whatever reason does not wish to, in effect, reimplement their own versions of a base-16 `strtoul`, as in the other answers. – Steve Summit Aug 01 '21 at 20:17
1
static char digits[] = "0123456789ABCDEF";

uint8_t *convert(uint8_t *chrs, uint8_t *buff)
{
    size_t len = strlen((char *)chrs);

    for(size_t i = 0; i < len; i++)
    {
        int is_first_digit = !(i & 1);
        int shift = is_first_digit << 2;
        buff[i / 2] += (strchr(digits, chrs[i]) - digits) << shift;
    }
    return buff;
}

int main(void)
{
    uint8_t String[] = "00AABBCC";
    uint8_t buff[4];

    convert(String, buff);
    for(size_t i = 0; i < sizeof(buff); i++)
    {
        printf("%hhx", buff[i]); // I know it is wrong format
    }
}

https://godbolt.org/z/9c8aexTvq

Or even faster solution:

int getDigit(uint8_t ch)
{
    switch(ch)
    {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            return ch - '0';
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
            return ch - 'A' + 10;
    }
    return 0;
}

uint8_t *convert(uint8_t *chrs, uint8_t *buff)
{
    size_t len = strlen((char *)chrs);
    
    for(size_t i = 0; i < len; i++)
    {
        int is_first_digit = !(i & 1);
        int shift = is_first_digit << 2;

        buff[i / 2] += (getDigit(chrs[i])) << shift;
    }
    return buff;
}

Remember: use functions for this kind of tasks. Do not program in main.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
0___________
  • 60,014
  • 4
  • 34
  • 74
  • Can I know the reason for DV? – 0___________ Jul 23 '21 at 13:28
  • @Daniel Kleinstein Thanks a lot for your help! Here's what I got by using your code on the line ` sscanf(String + i, "%2hhx", &arr[i / 2]); ` Modifiers are not allowed in printf_support=minimal mode main.c – ramles Jul 23 '21 at 13:28
  • @ramles Are you implementing for an embedded system? It's possible that your platform limits scanf functionality for performance reason (which might be configurable, check your IDE). I amended my answer to address this. – Daniel Kleinstein Jul 23 '21 at 13:42
  • 1
    Not the downvoter (in fact, an upvoter) but things like `(len >> 1) + (len & 1)` is not very readable – klutt Jul 23 '21 at 14:06
  • Also, the line `(strchr(digits, chrs[i]) - digits) << ((!(i & 1)) * 4)` is VERY hard to understand. I would split it up on a few lines to add clarity. – klutt Jul 23 '21 at 14:11
1

I took some inspiration from 0___________ and made my own:

static char digits[] = "0123456789ABCDEF";
void convert(uint8_t *chrs, uint8_t *buff)
{
    size_t len = strlen((char *)chrs);
    size_t i;

    for(i = 0; i < len; i+=2) {
        buff[i / 2] = (strchr(digits, chrs[i]) - digits);
        buff[i / 2] += (strchr(digits, chrs[i+1]) - digits) << 4;
    }

    if(i<len)
        buff[i / 2] = (strchr(digits, chrs[i]) - digits);
}

The changes are that I find it much more natural to do a complete element in every iteration. To account for odd length input strings, I just added an if statement in the end that takes care of it. This can be removed if input strings always have even length. And I skipped returning the buffer for simplicity. However, as 0___________ pointed out in comments, there are good reasons to return a pointer to the output buffer. Read about those reasons here: c++ memcpy return value

klutt
  • 30,332
  • 17
  • 55
  • 95
  • The return is in my answer for the purpose - the same as return values of stripy, memcpy etc. It allows function to be used in expressions and as a parameter of function calls. – 0___________ Jul 24 '21 at 17:52
  • @0___________ Very well, didn't think of those purposes – klutt Aug 01 '21 at 14:35
1

With your stipulation the strings will represent 4 bytes, this a far-easier-to-read-and-understand solution IMO. I have no comment on efficiency.

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <arpa/inet.h>

bool convert(const uint8_t* strValue, uint8_t* cvrtArray)
{
    // make 2nd parameter non-NULL for better error checking
    errno = 0;
    char* endptr = NULL;
    // convert to unsigned long
    unsigned long val = strtoul((const char*)strValue, &endptr, 16);
    // do some error checking, this probably needs some improvements
    if (errno == ERANGE && val == ULONG_MAX)
    {
        fprintf(stderr, "Overflow\n");

        return false;
    }
    else if ((strValue != NULL) && (*endptr != '\0'))
    {
        fprintf(stderr, "Cannot convert\n");
        return false;
    }

    // potential need to flip the bytes (your string is big endian, and the
    // test machine on godbolt is little endian)
    val = htonl(val);

    // copy to our array
    memcpy(cvrtArray, &val, 4);
    return true;
}

int main(void)
{
    uint8_t Data_Byte[4] = { 0 };
    uint8_t String[10] = "00AABBCC";

    if (convert(String, Data_Byte) == true)
    {
        for(size_t i=0; i<sizeof Data_Byte; i++)
        {
            printf("Data_Byte[%zu] = 0x%02" PRIX8 "\n", i, Data_Byte[i]);
        }
    }
    else
    {
        fprintf(stderr, "There was a problem converting %s to byte array\n", String);
    }

    return 0;
}

code in action

yano
  • 4,827
  • 2
  • 23
  • 35