0

Will the C89 standard allow me to cast void * to ptrdiff_t so I can print the memory location as hexadecimal?

For example:

static const char *dig = "0123456789abcdef";
char buf[16], *ptr = buf; /* Need 16 bytes when sizeof(void *) == 8 */
void *val;
ptrdiff_t tmp = (const unsigned char *) val - (const unsigned char *) 0;

do {
    *buf++ = dig[tmp & 0xf];  
    tmp >>= 4;  
} while (tmp);  

do {  
    putc(*--ptr);  
} while (ptr > buf);  

Context: I am writing a printf() function in kernel space.

cbot
  • 117
  • 6
  • The cast from a pointer to integer is implementation-defined. It's not clear how a `ptrdiff_t` lets you dump memory as hexadecimal. A `ptrdiff_t` can be added to a (non-void) pointer to produce another pointer, but you need a starting pointer. – Raymond Chen Sep 11 '21 at 20:02
  • @RaymondChen I added C code, hopefully it is more clear what I mean. – cbot Sep 11 '21 at 21:35
  • 1
    Are you trying to dump the memory, or print the value of the pointer? For the latter, `uintptr_t` is probably the better type to cast to. Don't try to subtract 0, just cast the pointer. If it's C89 and `uintptr_t` is unavailable, `size_t` is probably next best. You don't really want a signed type here; right-shifting a negative value is implementation-defined in C. – Nate Eldredge Sep 11 '21 at 21:37
  • 1
    You can cast it - you just have to accept that the result is implementation defined, so you need to know details about the compiler and the machine to interpret the results. – gnasher729 Sep 11 '21 at 21:41
  • @NateEldredge my bad I meant print the memory location. C89 only, so uintptr_t is unavailable. – cbot Sep 11 '21 at 22:19
  • @NateEldredge would casting size_t be undefined behaviour? – cbot Sep 11 '21 at 22:24
  • 1
    @cbot: Converting a pointer to an integer is valid and yields an implementation-defined result (though UB if the result doesn't fit in the integer type), C17 6.3.2.3p6. Here you have to rely on the compiler to do something reasonable, and give a result that is useful for your debugging purposes. A sane compiler just gives you the bits of the pointer as an integer, which is what you want. In theory `size_t` might be too narrow, but that's unlikely on a sane compiler, and even if it were, a sane compiler would just give you the low bits but cause no further problems. – Nate Eldredge Sep 11 '21 at 22:39
  • @NateEldredge unfortunately I can't rely on implementation-defined behaviour. I have the code already written but it uses a cast to an unsigned char * and reads the memory that way. Just would have been nicer to have something like uintptr_t or ptrdiff_t. – cbot Sep 11 '21 at 22:45
  • Well, if you can't rely on implementation-defined behavior, and especially if you only have C89, then you are going to get nowhere in writing a kernel. Standard C89 gives you no portable way to print out a pointer, short of `sprintf("%p")` which obviously you can't use. Subtracting the null pointer as you do is definitely UB; [subtraction is only defined for pointers within the same array](https://port70.net/~nsz/c/c89/c89-draft.html#3.3.6). – Nate Eldredge Sep 11 '21 at 22:48
  • @NateEldredge I have it working in a portable way but I had to use an unsigned char pointer and a little bit more work to calculate remainders from one byte to another especially for the octal format. It was way more work unfortunately but it actually turned out alright. – cbot Sep 11 '21 at 22:53
  • @NateEldredge no I don't need implementation defined behaviour to implement a kernel and ABI does not count as implementation defined behaviour. – cbot Sep 11 '21 at 23:01
  • 1
    Not sure what you mean by "I don't need implementation defined behavior." Even the line `void *val = (void *) 0xdeadbeef;` is implementation-defined behavior: "An arbitrary integer may be converted to a pointer. The result is implementation-defined." (section 3.3.4). – Raymond Chen Sep 12 '21 at 00:41
  • @RaymondChen yeah but "The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment." (A.7 Index). I mean things the compiler gets to choose and not architecture specific things. – cbot Sep 12 '21 at 01:05
  • 1
    The standard makes a strong recommendation, but the compiler technically can still choose whether to follow that recommendation or chart its own path, so the behavior is implementation-defined. (In practice, compilers will follow the recommendation. Note however, that even in standard-conforming systems that follow the recommendation, "p - 0" may not be the same as p. For example, on the 8086 in huge mode, `0x00100000` minus `0x0000000` is `0x00000100` when subtracted as pointers.) – Raymond Chen Sep 12 '21 at 01:14
  • @RaymondChen really? The 0xdeadbeef was more because I didn't have anything else. Thanks I'll keep that in mind, I didn't know the compiler could still choose something different. It's going to be tough to try and not use implementation defined behaviour when doing page table stuff in C (if it is even possible) in the kernel. – cbot Sep 12 '21 at 01:31
  • @RaymondChen: There's no guarantee that an integer-to-pointer cast of anything other than a null pointer constant ever yield a valid pointer. An implementation that doesn't define `uintptr_t` might not have any integer types large enough to hold enough bits to represent a valid pointer value. Were it not for C89's unwillingness to recognize optional features, integer-to-pointer casts should have been recognized as an optional feature, allowing implementations where such casts couldn't be meaningful to reject them outright. – supercat Sep 16 '21 at 23:02

0 Answers0