1

I want to know if there is any way to convert a string like "5ABBF13A000A01" to the next struct using struct and union method:

struct xSensorData2{
    unsigned char flags;           
    unsigned int refresh_rate;
    unsigned int timestamp;
};

Data should be:

flags = 0x01 (last byte);
refresh_rate = 0x000A (two bytes);
timestamp = 5ABBF13A (four bytes);

I'm thinking in next struct of data:

struct xData{
    union{
        struct{
            unsigned char flags:8;
            unsigned int refresh_rate:16;
            unsigned int timestamp:32;
        };
        char pcBytes[8];
    };
}__attribute__ ((packed));

But I got a struct of 12 bytes, I think it's because bit fields don't work for different types of data. I should just convert string to array of hex, copy to pcBytes and have access to each field.

Update: In stm32 platform i used this code:

bool bDecode_HexString(char *p)
{
uint64_t data = 0;                  /* exact width type for conversion 
*/
char *endptr = p;                   /* endptr for strtoul validation */
errno = 0;                                          /* reset errno */

data = strtoull (p, &endptr, 16);    /* convert input to uint64_t */

if (p == endptr && data == 0) {     /* validate digits converted */
    fprintf (stderr, "error: no digits converted.\n");
    return false;
}
if (errno) {    /* validate no error occurred during conversion */
    fprintf (stderr, "error: conversion failure.\n");
    return false;
}
//printf ("data: 0x%" PRIx64 "\n\n", data); /* output conerted string */

sensor.flags = data & 0xFF;                             /* set flags */
sensor.rate = (data >> CHAR_BIT) & 0xFFFF;              /* set rate */
sensor.tstamp = (data >> (3 * CHAR_BIT)) & 0xFFFFFFFF;  /* set timestamp */

    return true;

/* output sensor struct */
//    printf ("sensor.flags : 0x%02" PRIx8 "\nsensor.rate  : 0x%04" PRIx16
//            "\nsensor.tstamp: 0x%08" PRIx32 "\n", sensor.flags, sensor.rate,
//            sensor.tstamp);
}

and call the function:

char teste[50] = "5ABBF13A000A01";
bDecode_HexString(teste);

I get data = 0x3a000a01005abbf1

5
  • 2
    First, you want to convert the string to a 64 bit integer. Then I would not use union for reasons you are experiencing (struct padding, in particular). Why not just directly assign each member? I know it's not clever, but it's clear, reliable, and, in this case, concise since you only have 3 structure members. For example, something like, s.flags = (value & 0xff);, and s.refresh_rate = (value >> 8) & 0xffff; and s.timestamp = (value >> 24) & 0xffffffff;. Of course, this assumes you're using a 64 bit integer for value. Commented May 9, 2018 at 23:06
  • I would use strtoull to convert the string into a 64-bit value. And then use shifting and masking to extract the fields. Or you can break the string into three pieces, and convert each piece with strtoul. Commented May 9, 2018 at 23:07
  • Your structure would likely be 12 bytes on a 32-bit machine because of structure alignment to word boundaries. You have 8 bytes for the pcBytes field, and up to 32 bits = 4 bytes for the anonymous field. Commented May 9, 2018 at 23:40
  • Thanks @lurker. I will test this way.. Commented May 10, 2018 at 20:00
  • Yes @Mulliganaceous Commented May 10, 2018 at 20:02

2 Answers 2

2

If you are still struggling with the separation of your input into flags, rate & timestamp, then the suggestions in the comments regarding using an unsigned type to hold your input string converted to a value, and using the exact width types provided in <stdint.h>, will avoid potential problems inherent in manipulating signed types (e.g. potential sign-extension, etc.)

If you want to separate the values and coordinate those in struct, that is 100% fine. The work come in separating the individual flags, rate & timestamp. How you choose to store them so they are convenient within your code is up to you. A simple struct utilizing exact-width type could be:

typedef struct {    /* struct holding sensor data */
    uint8_t  flags;
    uint16_t rate;
    uint32_t tstamp;   
} sensor_t;

In a conversion from char * to uint64_t with strtoull, you have two primary validation checks:

  1. utilize both the pointer to the string to convert and the endptr parameter to validate that digits were in fact converted (strtoull sets endptr to point 1-character after the last digit converted). You use this to compare endptr with the original pointer for the data converted to confirm that a conversion took place (if no digits were converted the original pointer and endptr will be the same and the value returned will be zero); and

  2. you set errno = 0; before the conversion and then check again after the conversion to insure no error occurred during the conversion itself. If strtoull encounters an error, value exceeds range, etc.., errno is set to a positive value.

  3. (and if you have specific range validations, e.g. you want to store the result in a size less than that of the conversion, like uint32_t instead of uint64_t, you need to validate the final value can be stored in the smaller type)

A simple approach would be:

    uint64_t data = 0;                  /* exact width type for conversion */
    ...
    char *p = argc > 1 ? argv[1] : "0x5ABBF13A000A01",  /* input */
        *endptr = p;                    /* endptr for strtoul validation */
    errno = 0;          /* reset errno */
    ...
    data = strtoull (p, &endptr, 0);    /* convert input to uint64_t */

    if (p == endptr && data == 0) {     /* validate digits converted */
        fprintf (stderr, "error: no digits converted.\n");
        return 1;
    }
    if (errno) {    /* validate no error occurred during conversion */
        fprintf (stderr, "error: conversion failure.\n");
        return 1;
    }
    printf ("data: 0x%" PRIx64 "\n\n", data); /* output conerted string */

Finally, separating the value in data into the individual values of flags, rate & timestamp, can be done with simple shifts & ANDS, e.g.

    sensor_t sensor = { .flags = 0 };   /* declare instance of sensor */
    ...
    sensor.flags = data & 0xFF;                             /* set flags */
    sensor.rate = (data >> CHAR_BIT) & 0xFFFF;              /* set rate */
    sensor.tstamp = (data >> (3 * CHAR_BIT)) & 0xFFFFFFFF;  /* set timestamp */

    /* output sensor struct */
    printf ("sensor.flags : 0x%02" PRIx8 "\nsensor.rate  : 0x%04" PRIx16
            "\nsensor.tstamp: 0x%08" PRIx32 "\n", sensor.flags, sensor.rate,
            sensor.tstamp);

Putting it altogether, you could do something similar to:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <errno.h>
#include <limits.h>

typedef struct {    /* struct holding sensor data */
    uint8_t  flags;
    uint16_t rate;
    uint32_t tstamp;   
} sensor_t;

int main (int argc, char **argv) {

    uint64_t data = 0;                  /* exact width type for conversion */
    sensor_t sensor = { .flags = 0 };   /* declare instace of sensor */
    char *p = argc > 1 ? argv[1] : "0x5ABBF13A000A01",  /* input */
        *endptr = p;                    /* endptr for strtoul validation */
    errno = 0;          /* reset errno */

    data = strtoull (p, &endptr, 0);    /* convert input to uint64_t */

    if (p == endptr && data == 0) {     /* validate digits converted */
        fprintf (stderr, "error: no digits converted.\n");
        return 1;
    }
    if (errno) {    /* validate no error occurred during conversion */
        fprintf (stderr, "error: conversion failure.\n");
        return 1;
    }
    printf ("data: 0x%" PRIx64 "\n\n", data); /* output conerted string */

    sensor.flags = data & 0xFF;                             /* set flags */
    sensor.rate = (data >> CHAR_BIT) & 0xFFFF;              /* set rate */
    sensor.tstamp = (data >> (3 * CHAR_BIT)) & 0xFFFFFFFF;  /* set timestamp */

    /* output sensor struct */
    printf ("sensor.flags : 0x%02" PRIx8 "\nsensor.rate  : 0x%04" PRIx16
            "\nsensor.tstamp: 0x%08" PRIx32 "\n", sensor.flags, sensor.rate,
            sensor.tstamp);

    return 0;
}

Example Use/Output

$ ./bin/struct_sensor_bitwise
data: 0x5abbf13a000a01

sensor.flags : 0x01
sensor.rate  : 0x000a
sensor.tstamp: 0x5abbf13a

Look things over and let me know if you have further questions.

Sign up to request clarification or add additional context in comments.

15 Comments

Good comment, will do. And good catch, I'm so used to 64-bit boxes today, those 4-bit longs sneak by every now and then.
Thanks @david-c-rankin. I will use your method.
Just one question. How errno will validate if one error occur?
man strtoll "If an underflow occurs, strtoll()returns LLONG_MIN. If an overflow occurs, strtol() returns LLONG_MAX. In both cases, errno is set to ERANGE." (LL in context for L)
i'm using this in STM32 microcontroller, so i don't know if all things in manual applies to this. I'm testing this code microcontroller and some strange happens. My string is 5ABBF13A001E01 and strtoll returns 0x3a001e01005abbf1.Do you know why?
|
1

Here, you have a string of length 14, representing a 7-byte value, consisting of 14 hexadecimal values. Consider this code using strtol, which hexadecimally converts your string into a long int, and then decodes it digit-wise.

uint64_t n = strtoul(str, NULL, 16); // Convert to hexadecimal
int flags = n % 0x10;
int refresh_rate = (n / 0x100) % 0x100000;
int timestamp = n / 0x1000000;

Here is a test case (#import <stdlib.h>):

char str[16] = "5ABBF13EE00AFF";
uint64_t n = strtoul(str, NULL, 16); // Convert to hexadecimal

// Use divide and mod to extract digit segment
int flags = n % 0x100;
int refresh_rate = (n / 0x100) % 0x10000;
int timestamp = n / 0x1000000;
// Print timestamp, refresh rate, and flags
printf("%p %p %p", timestamp, refresh_rate, flags);

The expected result is 0x5abbf13e 0xe00a 0xff as expected.

1 Comment

Recommend strtoul and long long unsigned, or better include stdint.h and use uint64_t exact width type to avoid potential problems with signed values.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.