You’re doing it right.
From the manual page:
o, u, x, X
The unsigned int argument is converted to unsigned octal (o), unsigned decimal (u), or unsigned hexadecimal (x and X) notation.
So the value for x should always be unsigned. To make it long in size, use:
l
(ell) A following integer conversion corresponds to a long int or unsigned long int argument […]
So %lx is unsigned long. An address (pointer value), however, should be printed with %p and cast to void *.