Add leading zeros to HEX string

Hello!

I am new to Arduino and, in my project, I'm trying to print via Serial some hexadecimal strings (these strings are stored in some uint32_t variabiles) with Serial.print(hex_string, HEX), but unfortunately, whenever the string starts with zeros, the leading zeros do not get printed.

I also tried to store my hex string into a char array as shown below:

  char hash[5];

  sprintf(hash,"%04X", info.H[0]);

  Serial.print("\nHash: ");

  Serial.println(hash);

(where info.H[0] is my hex string stored in a uint32_t variabile), but this prints out some random values.

Is there a way to print hexadecimal strings with leading zeros?

Example:

uint32_t hex_string = 0x00ffffff
Serial.print(hex_string, HEX);

should print exactly "00ffffff".

Thank you very much.

This won’t work, because you forgot to allocate enough space for the zero byte terminator. Six bytes are required to store 5 characters.

  char hash[5];

  sprintf(hash,"%04X", info.H[0]);

When using Serial.print, your program can insert arbitrary leading zeros. For example

if (x <= 0x0F) Serial.print("0");
Serial.println(x,HEX);

jremington:
This won’t work, because you forgot to allocate enough space for the zero byte terminator. Six bytes are required to store 5 characters.

  char hash[5];

sprintf(hash,"%04X", info.H[0]);




When using Serial.print, your program can insert arbitrary leading zeros. For example



if (x <= 0x0F) Serial.print(“0”);
Serial.println(x,HEX);

Which five characters?
Maybe the problem is in the unposted code.

Oops, I counted the "X", so it is OK after all.

I guess

sprintf(hash,"%04X", *((uint16_t*)info.H));

could work.

Whandall:
I guess

sprintf(hash,"%04X", *((uint16_t*)info.H));

could work.

You’re not allowed to do that. info.H decays to uint32_t *, you’re not allowed to cast it to uint16_t *. (Well, you’re allowed to perform the cast itself, but dereferencing the resulting pointer is illegal.)

That’s why C-style casts are so evil, they suppress all errors and even compiler warnings. If you would have tried to cast using static_cast (which is the preferred method in C++), the compiler would have complained:

invalid static_cast from type 'uint32_t* {aka long unsigned int*}' to type 'uint16_t* {aka unsigned int*}'.


If you want to print all 8 hexadecimal digits, you can use

[color=#00979c]void[/color] [color=#5e6d03]setup[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]begin[/color][color=#000000]([/color][color=#000000]115200[/color][color=#000000])[/color][color=#000000];[/color]
  [color=#00979c]uint32_t[/color] [color=#000000]hex_string[/color] [color=#434f54]=[/color] [color=#000000]0x00ffffff[/color][color=#000000];[/color]
  [color=#00979c]char[/color] [color=#000000]buff[/color][color=#000000][[/color][color=#000000]9[/color][color=#000000]][/color][color=#000000];[/color]
  [color=#000000]static_assert[/color][color=#000000]([/color][color=#00979c]sizeof[/color][color=#000000]([/color][color=#00979c]unsigned[/color] [color=#00979c]long[/color][color=#000000])[/color] [color=#434f54]==[/color] [color=#00979c]sizeof[/color][color=#000000]([/color][color=#000000]hex_string[/color][color=#000000])[/color][color=#434f54],[/color] 
                [color=#005c5f]"Incorrect format specifier lX for uint32_t"[/color][color=#000000])[/color][color=#000000];[/color]
  [color=#d35400]sprintf[/color][color=#000000]([/color][color=#000000]buff[/color][color=#434f54],[/color] [color=#005c5f]"%08lX"[/color][color=#434f54],[/color] [color=#000000]hex_string[/color][color=#000000])[/color][color=#000000];[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color][color=#000000]([/color][color=#000000]buff[/color][color=#000000])[/color][color=#000000];[/color]
[color=#000000]}[/color]
[color=#00979c]void[/color] [color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color][color=#000000]}[/color]

If you only want the 4 least significant digits:

  [color=#00979c]uint32_t[/color] [color=#000000]hex_string[/color] [color=#434f54]=[/color] [color=#000000]0x00ffffff[/color][color=#000000];[/color]
  [color=#00979c]char[/color] [color=#000000]buff[/color][color=#000000][[/color][color=#000000]5[/color][color=#000000]][/color][color=#000000];[/color]
  [color=#000000]static_assert[/color][color=#000000]([/color][color=#00979c]sizeof[/color][color=#000000]([/color][color=#00979c]unsigned[/color] [color=#00979c]int[/color][color=#000000])[/color] [color=#434f54]==[/color] [color=#00979c]sizeof[/color][color=#000000]([/color][color=#00979c]uint16_t[/color][color=#000000])[/color][color=#434f54],[/color] 
                [color=#005c5f]"Incorrect format specifier X for uint16_t"[/color][color=#000000])[/color][color=#000000];[/color]
  [color=#d35400]sprintf[/color][color=#000000]([/color][color=#000000]buff[/color][color=#434f54],[/color] [color=#005c5f]"%04X"[/color][color=#434f54],[/color] [color=#00979c]uint16_t[/color][color=#000000]([/color][color=#000000]hex_string[/color][color=#000000])[/color][color=#000000])[/color][color=#000000];[/color]

Pieter

The information given did not include the type of the array H.

So I can regard everything after "info.H decays to uint32_t *" as unfounded.

Whandall:
The information given did not include the type of the array H.

mdonati11:
I'm trying to print via Serial some hexadecimal strings (these strings are stored in some uint32_t variabiles)
...
(where info.H[0] is my hex string stored in a uint32_t variabile)

Even without that information, your code would be invalid in all cases, except if the type of info.H were uint16_t *, in which case the cast would be unnecessary.

PieterP:
your code would be invalid in all cases

That is not true.

So what? It was a guess. Without information on the type of H that's all you can do. Guess.

Whandall:
So what? It was a guess. Without information on the type of H that's all you can do. Guess.

The type of H was explicitly mentioned by OP.
But that's not the point, the point is that the code you posted either invokes undefined behavior, or includes an superfluous C-style cast, promoting bad programming practices either way.

Whandall:
That is not true.

Why not?

Any datatype that has the wished value in it's first two bytes of memory would work.

If H were really was the OP specified (where info.H[0] is my hex string stored in a uint32_t variabile),
the sprintf would have worked as expected.

But you are right, casts are bad.

Have a nice day.

Whandall:
Any datatype that has the wished value in it's first two bytes of memory would work.

It might work by accident, but it invokes undefined behavior. There are specific rules about casts to different types, and casting a pointer to uint32_t to a pointer to uint16_t violates the strict aliasing rule.

In general, you can only cast pointer types that are "similar" (uint16_t and uint32_t are not similar). Casting any type of pointer to a pointer to unsinged char, char or std::byte are exceptions, and are allowed to inspect the memory layout of objects.
Pretty much all other casts are illegal (strictly speaking, dereferencing the resulting pointer is illegal, the cast itself is legal, but dangerous and bad practice).

You can read more here: reinterpret_cast conversion - cppreference.com

As you said, it'll probably work fine in simple cases like these - after all, if you think about the memory layout, it makes sense. The thing to keep in mind is that you're programming for the C++ abstract machine, so all that matters are the rules imposed by the C++ standard, you can't rely on the memory layout being the same if you break these rules, because then the program is invalid.
I've had problems in code bases that used similar casts, where if you added a print statement 10 lines further down, suddenly, it wouldn't work anymore because of the undefined behavior invoked by the invalid cast.

PieterP:
You're not allowed to do that. info.H decays to uint32_t *, you're not allowed to cast it to uint16_t *. (Well, you're allowed to perform the cast itself, but dereferencing the resulting pointer is illegal.)

That's why C-style casts are so evil, they suppress all errors and even compiler warnings. If you would have tried to cast using static_cast (which is the preferred method in C++), the compiler would have complained:

invalid static_cast from type 'uint32_t* {aka long unsigned int*}' to type 'uint16_t* {aka unsigned int*}'.


If you want to print all 8 hexadecimal digits, you can use

[color=#00979c]void[/color] [color=#5e6d03]setup[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]

Serial.begincolor=#000000[/color];
 uint32_t hex_string = 0x00ffffff;
 char buff[9];
 static_assert(sizeof(unsigned long) == sizeofcolor=#000000[/color],
               "Incorrect format specifier lX for uint32_t");
 sprintf(buff, "%08lX", hex_string);
 Serial.printlncolor=#000000[/color];
}
void loopcolor=#000000[/color] {}





If you only want the 4 least significant digits:


uint32_t hex_string = 0x00ffffff;
 char buff[5];
 static_assert(sizeof(unsigned int) == sizeofcolor=#000000[/color],
               "Incorrect format specifier X for uint16_t");
 sprintf(buff, "%04X", uint16_tcolor=#000000[/color]);





Pieter

Thank you very much!

When debugging low-level hardware stuff, I too like to print values in hex format with all the digits properly lined up. I also add the “0x” in front for good measure. I find these two template functions to be useful for this. They’ll work for variables from uint8_t to uint64_t. You can adjust the print formatting as you see fit.

#include "Arduino.h"
template<typename IntType>
void printHex(IntType val, Print *ptr = &Serial) {
  Serial.print("0x ");
  for (int8_t shift = 8 * sizeof(val) - 4; shift >= 0; shift -= 4) {
    uint8_t hexDigit = (val >> shift) & 0xF;
    ptr->print(hexDigit, HEX);
    if (((shift & 0xF) == 0) && (shift > 0)) {
      ptr->print(" ");
    }
  }
}

template<typename IntType>
void printlnHex(IntType val, Print *ptr = &Serial) {
  printHex(val, ptr);
  ptr->println();
}