Pretty Print C++ objects or arbitrary ranges of memory to hexadecimal or binary form.
git clone https://github.com/tomasguillen/hexer.git
Modify your CMakeLists.txt:
add_subdirectory(hexer)
add_executable(target_name ...
target_link_libraries(target_name PRIVATE Hexer ...
and in your code add:
#include <hexer.hpp>
...
git clone https://github.com/tomasguillen/hexer.git
then in your code add:
#include "hexer/hexer.hpp"
...
Let's say we want to print a simple object, and we want to highlight the bytes of the id member (these two function calls will do the same):
struct TestObject {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
std::string id{"default"};
};
TestObject object{};
std::cout << "\nObject as hex, highlight bytes of TestObject::id member:";
hexer::print_object_as_hex(object, offsetof(TestObject, id),
sizeof(std::string));
// same as above
std::cout << "\nAddressRange as hex, highlight bytes of TestObject::id member:";
hexer::print_address_range_as_hex(object, sizeof(TestObject),
offsetof(TestObject, id),
sizeof(std::string), 0);This will print:
Object as hex, highlight bytes of TestObject::id member:
00000000 0000 0000 0000 0000 0001 0000 0000 0000
00000016 8d60 3ebd 7fff 0000 0007 0000 0000 0000 // <- this are the bytes
00000032 6564 6166 6c75 0074 d740 4bc8 5588 0000 // of our "id" string
00000048
address_range as hex, highlight bytes of TestObject::id member:
00000000 0000 0000 0000 0000 0001 0000 0000 0000
00000016 8d60 3ebd 7fff 0000 0007 0000 0000 0000 // same as above
00000032 6564 6166 6c75 0074 d740 4bc8 5588 0000 //
00000048
Let's say we want to print a more complex object, and we want to highlight the bytes of the id member(these three function calls will do the same):
struct TestObject {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
std::string id{"default"};
};
struct TestObjectComposed {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
TestObject simple_object{};
std::string id_outer{"outerdefault"};
};
TestObjectComposed composed_object{};
std::cout << "\nObject as hex, highlight bytes of "
"TestObjectComposed::id_outer member:";
hexer::print_object_as_hex(composed_object,
offsetof(TestObjectComposed, id_outer),
sizeof(std::string));
// same as above
std::cout << "\nAddressRange as hex, highlight bytes of "
"TestObjectComposed::id_outer member:";
hexer::print_address_range_as_hex(composed_object, sizeof(TestObjectComposed),
offsetof(TestObjectComposed, id_outer),
sizeof(std::string), 0);
// same as above
std::cout << "\nAddressRange as hex, highlight bytes of "
"TestObjectComposed::id_outer member (without last argument "
"'start_offset'):";
hexer::print_address_range_as_hex(composed_object, sizeof(TestObjectComposed),
offsetof(TestObjectComposed, id_outer),
sizeof(std::string), 0);This will print:
Object as hex, highlight bytes of TestObjectComposed::id_outer member:
00000000 0000 0000 0000 0000 0001 0000 7f59 0000
00000016 0000 0000 0000 0000 0001 0000 7f59 0000
00000032 6f50 9313 7ffd 0000 0007 0000 0000 0000
00000048 6564 6166 6c75 0074 ff90 de2f 7f59 0000
00000064 6f70 9313 7ffd 0000 000c 0000 0000 0000
00000080 756f 6574 6472 6665 7561 746c 5500 0000
00000096
AddressRange as hex, highlight bytes of TestObjectComposed::id_outer member:
00000000 0000 0000 0000 0000 0001 0000 7f59 0000
00000016 0000 0000 0000 0000 0001 0000 7f59 0000
00000032 6f50 9313 7ffd 0000 0007 0000 0000 0000
00000048 6564 6166 6c75 0074 ff90 de2f 7f59 0000
00000064 6f70 9313 7ffd 0000 000c 0000 0000 0000
00000080 756f 6574 6472 6665 7561 746c 5500 0000
00000096
AddressRange as hex, highlight bytes of TestObjectComposed::id_outer member (without last argument 'start_offset'):
00000000 0000 0000 0000 0000 0001 0000 7f59 0000
00000016 0000 0000 0000 0000 0001 0000 7f59 0000
00000032 6f50 9313 7ffd 0000 0007 0000 0000 0000
00000048 6564 6166 6c75 0074 ff90 de2f 7f59 0000
00000064 6f70 9313 7ffd 0000 000c 0000 0000 0000
00000080 756f 6574 6472 6665 7561 746c 5500 0000
Let's say we want to print a more complex object, and we want to highlight the bytes of the id member: print_address_range_as_hex allows us to control the ranges for both the printing and the highlight area.
struct TestObject {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
std::string id{"default"};
};
struct TestObjectComposed {
int just_an_int_outer{}, just_an_int_outer2{}, just_an_int_outer3 = 1;
TestObject simple_object{};
std::string id_outer{"outerdefault"};
};
TestObjectComposed composed_object{};
std::cout
<< "\nprint_address_range_as_hex (printing the 'simple_object' member "
"and highlighting the bytes of its 'just_an_int3' member):";
hexer::print_address_range_as_hex(
composed_object, // object we want to print
offsetof(TestObjectComposed,
id_outer), // we will stop printing
// bytes at the start of id_outer address
offsetof(
TestObjectComposed,
simple_object.just_an_int3), // we will highlight from the start of
// simple_object.just_an_int3 address
sizeof(composed_object.simple_object
.just_an_int3), // we will stop highlighting at the end of
// simple_object.just_an_int3
offsetof(TestObjectComposed,
simple_object)); // we will start printing bytes at the start of
// simple_object addressThis will print:
Here we use print_address_range_as_hex to print the 'simple_object' member and highlighting the bytes of its 'just_an_int3' member):
00000000 0000 0000 0000 0000 0001 0000 0000 0000
00000032 52b0 8299 7ffc 0000 0007 0000 0000 0000
00000048 6564 6166 6c75 0074 7717 4b9d 7fd7 0000
00000064
We can do something similar with print_object_as_hex by just passing composed_object.simple_object directly:
struct TestObject {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
std::string id{"default"};
};
struct TestObjectComposed {
int just_an_int_outer{}, just_an_int_outer2{}, just_an_int_outer3 = 1;
TestObject simple_object{};
std::string id_outer{"outerdefault"};
};
TestObjectComposed composed_object{};
std::cout
<< "\nprint_object_as_hex (printing the member 'simple_object' "
"and highlighting the bytes of its 'just_an_int3' member):";
hexer::print_object_as_hex(
composed_object.simple_object, // object we want to print
offsetof(
TestObjectComposed,
simple_object.just_an_int3), // we will highlight from the start of
// simple_object.just_an_int3 address
sizeof(composed_object.simple_object
.just_an_int3)); // we will stop highlighting at the end of
// simple_object.just_an_int3Same as example 3 using print_object_as_hex but with everything printed as bits and in columns of 4:
struct TestObject {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
std::string id{"default"}; // <-- We want to highlight id member
};
TestObject object{};
std::cout << "EXAMPLE4: \nObject as hex, highlight bytes of TestObject::id member:";
hexer::print_object_as_hex<hexer::print_as_bits, 4, hexer::print_as_bits>(
object, offsetof(TestObject, id), sizeof(std::string));This will print:
Object as hex, highlight bytes of TestObject::id member:
00000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000
00000008 0000000000000001 0000000000000000 0111111011111111 0000000000000000
00000016 0000101101000000 1101100100001000 0111111111111101 0000000000000000
00000024 0000000000000111 0000000000000000 0000000000000000 0000000000000000
00000032 0110010101100100 0110000101100110 0110110001110101 0000000001110100
00000040 1100000110001000 0101110000010000 0111111011111111 0000000000000000
00000048
Just Pass An Object To Print (optional highlight range)
template <void PrintFunction(const char) = print_as_hex,
size_t NewLineAfterNElements = 8,
void HighlightedPrintFunction(const char) = print_as_hex, class Type>
inline void print_object_as_hex(
const Type &object, const std::optional<size_t> highlight_offset = {},
const std::optional<size_t> highlight_size = {})pass an object and/or an optional highlight range [offset from object address, size of range]\
pretty prints the object memory address in hexadecimal, with optional highlighting
- PrintFunction: A function that prints a single character. Used to format each character. Hexer has 2 available functions(default =print_as_hex):
a.
c++print_as_hex('\0')will print00;c++print_as_bits('a')will print61
inline void print_as_hex(const char character) b.c++print_as_bits('\0')will print00000000;c++print_as_bits('a')will print01100001
template <size_t BitsToPrint = 8>
inline void print_as_bits(const char character) - NewLineAfterNElements: The number of columns our output will have defaults to 8
- HighlightedPrintFunction: Same as PrintFunction but for highlighted bytes. This way we can have highlighted bytes in hex or binary, independently from PrintFunction.
- object: The object we want hexer to print.
- highlight_offset: An offset at which hexer will start highlighting bytes.
- highlight_size: A size after which hexer will stop highlighting bytes.
And Object And Size To Print (optional highlight range, and optional start_offset for the print range)
template <void PrintFunction(const char) = print_as_hex,
size_t NewLineAfterNElements = 8,
void HighlightedPrintFunction(const char) = print_as_hex, class Type>
inline void print_address_range_as_hex(
const Type &object, size_t size,
std::optional<size_t> highlight_offset = {},
std::optional<size_t> highlight_size = {},
std::optional<size_t> start_offset = {})pass an object pointer and a size [offset from object address], and/or an optional highlight range [offset from object address, size of range], and or an optional start_offset
pretty prints the address range up to the address of object+size in hexadecimal with optional highlighting and start_offset
- PrintFunction: A function that prints a single character. Used to format each character. Hexer has 2 available functions(default =print_as_hex):
a.
c++print_as_hex('\0')will print00;c++print_as_bits('a')will print61
inline void print_as_hex(const char character) b.c++print_as_bits('\0')will print00000000;c++print_as_bits('a')will print01100001
template <size_t BitsToPrint = 8>
inline void print_as_bits(const char character) - NewLineAfterNElements: The number of columns our output will have defaults to 8
- HighlightedPrintFunction: Same as PrintFunction but for highlighted bytes. This way we can have highlighted bytes in hex or binary, independently from PrintFunction.
- object: The object we want hexer to print.
- size: An offset at which hexer will stop printing bytes.
- highlight_offset: An offset at which hexer will start highlighting bytes.
- highlight_size: A size after which hexer will stop highlighting bytes.
- start_offset: An offset from the start of the object at which hexer will start printing the bytes
Let's say we want to inspect the bits of composed_object.simple_object.just_an_int3, suppose we want to start printing bytes as hexadecimal but have the highlighted bytes in binary format:
struct TestObject {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
std::string id{"default"}; // <-- We want to highlight id member
};
struct TestObjectComposed {
int just_an_int_outer{}, just_an_int_outer2{}, just_an_int_outer3 = 1;
TestObject simple_object{};
std::string id_outer{"outerdefault"};
};
TestObjectComposed composed_object{};
std::cout << "\nAddress of composed_object.simple_object as hexadecimal with composed_object.simple_object.just_an_int3 highlighted and in binary format";
hexer::print_address_range_as_hex<hexer::print_as_hex, 8,
hexer::print_as_bits>(
composed_object, // object we want to print
offsetof(TestObjectComposed,
id_outer), // we will stop printing
// bytes at the start of id_outer address
offsetof(
TestObjectComposed,
simple_object.just_an_int3), // we will highlight from the start of
// simple_object.just_an_int3 address
sizeof(composed_object.simple_object
.just_an_int3), // we will stop highlighting at the end of
// simple_object.just_an_int3
offsetof(TestObjectComposed,
simple_object)); // we will start printing bytes at the start of
// simple_object addressThis will print:
Address of composed_object.simple_object as hexadecimal with composed_object.simple_object.just_an_int3 highlighted and in binary format
00000000 0000 0000 0000 0000 0000000000000001 0000000000000000 7f8f 0000
00000032 abd0 43b2 7fff 0000 0007 0000 0000 0000
00000048 6564 6166 6c75 0074 0000 0000 0000 0000
00000064
We can achieve the same with print_object_as_hex:
struct TestObject {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
std::string id{"default"}; // <-- We want to highlight id member
};
struct TestObjectComposed {
int just_an_int_outer{}, just_an_int_outer2{}, just_an_int_outer3 = 1;
TestObject simple_object{};
std::string id_outer{"outerdefault"};
};
TestObjectComposed composed_object{};
std::cout << "\nAddress of composed_object.simple_object as hexadecimal with "
"composed_object.simple_object.just_an_int3 highlighted and in "
"binary format";
hexer::print_object_as_hex<hexer::print_as_hex, 8,
hexer::print_as_bits>(
composed_object.simple_object, // object we want to print
offsetof(
TestObjectComposed,
simple_object.just_an_int3), // we will highlight from the start of
// simple_object.just_an_int3 address
sizeof(composed_object.simple_object
.just_an_int3)); // we will stop highlighting at the end of
// simple_object.just_an_int3Sometimes you may want to print the raw memory of an array or vector, you can do this with print_address_range_as_hex_unchecked. Like this:
struct TestObject {
int just_an_int{}, just_an_int2{}, just_an_int3 = 1;
std::string id{"default"};
};
std::array<TestObject, 2> array_of_testobjects{};
std::cout << "Size of TestObject:" << sizeof(TestObject) << '\n';
hexer::print_address_range_as_hex_unchecked(array_of_testobjects, 50, 5, 8, 4);
// same but for vector
std::vector<TestObject> vector_of_testobjects(2);
hexer::print_address_range_as_hex_unchecked(*vector_of_testobjects.data(), 50, 5, 8, 4);If we were to use print_address_range_as_hex instead, hexer will give an error if compiled in debug mode. The reason is print_address_range_as_hex checks that the range you want to print is less or equal to the sizeof(ObjectType), since we are skipping 4 bytes then printing 50, that's more than what the type could hold(48 in this case). Since we aren't dealing with a single object, we have to call print_address_range_as_hex_unchecked to disable the size check. You could also just loop through the vector and print each object individually, but this allows you to print addresses between elements if needed.