Reputation: 33385
Given
unsigned long long int strtoull (const char* str, char** endptr, int base);
Is this valid?
unsigned char *s = "123";
unsigned char *t;
unsigned long long n = strtoull(s, &t, 0);
It's not generally okay to take a pointer to one type and cast and use it as a pointer to another, and strictly speaking char
and unsigned char
are different types, so while there is a sense in which the above code is clearly reasonable, I want to make sure it's not undefined behavior by the strict letter of the standard.
Upvotes: 2
Views: 110
Reputation: 222536
There is no aliasing problem regarding the types, for two reasons. The aliasing rules in C 2018 6.5 7 permit accessing an object with a character type (and unsigned char
and char
are both character types), and they also permit accessing an object with a type that is the signed or unsigned type corresponding to the effective type of the object (which you are doing with unsigned char
and char
).
However, the use of unsigned char *
as an argument for a const char *
parameter violates rules about compatible types, as does the use of an unsigned char **
argument for a char **
parameter. In the first case, if you insert an explicit cast to the parameter type, the conversion is defined by C 2018 6.3.2.3 7: “When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object.”
In the second case, I do not see that the C standard defines a way to convert an unsigned char **
to char **
with a defined result. (The conversion itself is permitted, but the value of the result is not specified by the C standard except that it will yield something equivalent to the original pointer when converted back to unsigned char **
.)
Upvotes: 2
Reputation: 15134
You can work around the type discrepancy if the possibility of a compiler breaking your program because it converts char**
to unsigned char**
is a real concern to you.
#include <stdlib.h>
unsigned long long foo(const unsigned char* const p)
{
const char* const s = (const char*)p;
char* t = NULL;
const unsigned long long n = strtoull(s, &t, 0);
return n;
}
In this simple example, I might omit the declaration of s
and just use casts wherever I need them, and there is no purpose in declaring t
at all. Declaring them as aliases makes more sense if you use the aliases often and they save keystrokes. (Also, with types other than character types and void*
, you would want to make the alias a function parameter so that it does not appear in the same scope as the original and violate the strict-aliasing rules.)
If you need to pass a value of end_ptr
other than NULL
, presumably you’re using the stored value to continue parsing. Either you’ll be passing it to another function like strtoull()
that expects a char*
, or else you can make a safe conversion to unsigned char*
, a conversion between pointers to compatible types that follows the aliasing rules. This raises none of the issues of (char**)&uchar_ptr
.
The code will actually compile without any explicit cast, but the cast makes it clearer that the aliasing is intentional (and suppresses a compiler warning).
Upvotes: 1