Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   C Programming (http://www.velocityreviews.com/forums/f42-c-programming.html)
-   -   Writing through an unsigned char pointer (http://www.velocityreviews.com/forums/t959633-writing-through-an-unsigned-char-pointer.html)

Noob 04-11-2013 01:52 PM

Writing through an unsigned char pointer
 
Hello,

Is the following code valid:

static void foo(unsigned char *buf)
{
int i;
for (i = 0; i < 16; ++i) buf[i] = i;
}

void bar(void)
{
unsigned long arr[4];
foo(arr);
}

The compiler points out that (unsigned long *) is not
compatible with (unsigned char *).

So I cast to the expected type:

void bar(void)
{
unsigned long arr[4];
foo((unsigned char *)arr);
}

I think it is allowed to cast to (unsigned char *)
but I don't remember if it's allowed only to inspect
(read) the values, or also to set them. Also the fact
the "real" type is unsigned means there are no trap
representations, right?

Regards.

Nobody 04-11-2013 02:24 PM

Re: Writing through an unsigned char pointer
 
On Thu, 11 Apr 2013 15:52:22 +0200, Noob wrote:

> I think it is allowed to cast to (unsigned char *)
> but I don't remember if it's allowed only to inspect
> (read) the values, or also to set them.


It's allowed, but it isn't specified what the result will be.

An unsigned long can be any number of bytes (so long as it's at least 32
bits, which isn't necessarily the same thing as 4 bytes); on the most
common platforms, it will be either 4 bytes or 8 bytes. The byte order can
be big-endian, little-endian, Vax-endian or something else.


James Kuyper 04-11-2013 02:32 PM

Re: Writing through an unsigned char pointer
 
On 04/11/2013 09:52 AM, Noob wrote:
> Hello,
>
> Is the following code valid:
>
> static void foo(unsigned char *buf)
> {
> int i;
> for (i = 0; i < 16; ++i) buf[i] = i;
> }
>
> void bar(void)
> {
> unsigned long arr[4];
> foo(arr);
> }


This code assumes that sizeof(arr) == 16, or equivalently,
sizeof(long)==4. You should either make the behavior of foo() depend
upon sizeof(long), or at least put in assert(sizeof(long)==4).

> The compiler points out that (unsigned long *) is not
> compatible with (unsigned char *).
>
> So I cast to the expected type:
>
> void bar(void)
> {
> unsigned long arr[4];
> foo((unsigned char *)arr);
> }
>
> I think it is allowed to cast to (unsigned char *)
> but I don't remember if it's allowed only to inspect
> (read) the values, or also to set them. ...


It's allowed, for both purposes.

> ... Also the fact
> the "real" type is unsigned means there are no trap
> representations, right?


Footnote 53 of n1570.pdf says, with respect to unsigned integer types,
that "Some combinations of padding bits might generate trap
representations." Unsigned char isn't allowed to have any padding bits,
but unsigned long certainly can.
--
James Kuyper

Jorgen Grahn 04-11-2013 05:09 PM

Re: Writing through an unsigned char pointer
 
On Thu, 2013-04-11, Noob wrote:
> Hello,
>
> Is the following code valid:
>
> static void foo(unsigned char *buf)
> {
> int i;
> for (i = 0; i < 16; ++i) buf[i] = i;
> }
>
> void bar(void)
> {
> unsigned long arr[4];
> foo(arr);
> }
>
> The compiler points out that (unsigned long *) is not
> compatible with (unsigned char *).
>
> So I cast to the expected type:
>
> void bar(void)
> {
> unsigned long arr[4];
> foo((unsigned char *)arr);
> }
>
> I think it is allowed to cast to (unsigned char *)
> but I don't remember if it's allowed only to inspect
> (read) the values, or also to set them. Also the fact
> the "real" type is unsigned means there are no trap
> representations, right?


Don't know what the language guarantees.

Even if I knew about trap representations and stuff, and knew a long
is four chars on my target, it would worry me that I have no idea what
the 16 chars look like when viewed as 4 longs. I would have
introduced endianness issues into the program, and that's never a good
thing -- they tend to spread.

If I were you, at this point I'd sidestep the problem by rewriting the
code without unusual casts. I don't think I've ever seen a problem
which could be solved by things like the casting above, but not by
everyday code without casts. (Ok, except for badly written third-party
APIs, perhaps.)

You don't show the problem you're trying to solve, so I cannot suggest
an alternative (except for the obvious and trivial change to bar()).

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

Tim Rentsch 04-11-2013 11:18 PM

Re: Writing through an unsigned char pointer
 
Nobody <nobody@nowhere.com> writes:

> On Thu, 11 Apr 2013 15:52:22 +0200, Noob wrote:
>
>> I think it is allowed to cast to (unsigned char *)
>> but I don't remember if it's allowed only to inspect
>> (read) the values, or also to set them.

>
> It's allowed, but it isn't specified what the result will be.
> [snip]


Not quite right. The Standard does specify the behavior in
such cases, as implementation-defined. So even though the
results won't be portable, you can find out what they will
be.

Tim Rentsch 04-11-2013 11:42 PM

Re: Writing through an unsigned char pointer
 
Jorgen Grahn <grahn+nntp@snipabacken.se> writes:

> On Thu, 2013-04-11, Noob wrote:
>> Hello,
>>
>> Is the following code valid:
>>
>> static void foo(unsigned char *buf)
>> {
>> int i;
>> for (i = 0; i < 16; ++i) buf[i] = i;
>> }
>>
>> void bar(void)
>> {
>> unsigned long arr[4];
>> foo(arr);
>> }
>>
>> The compiler points out that (unsigned long *) is not
>> compatible with (unsigned char *).
>>
>> So I cast to the expected type:
>>
>> void bar(void)
>> {
>> unsigned long arr[4];
>> foo((unsigned char *)arr);
>> }
>>
>> I think it is allowed to cast to (unsigned char *)
>> but I don't remember if it's allowed only to inspect
>> (read) the values, or also to set them. Also the fact
>> the "real" type is unsigned means there are no trap
>> representations, right?

>
> Don't know what the language guarantees.
>
> Even if I knew about trap representations and stuff, and knew a long
> is four chars on my target, it would worry me that I have no idea what
> the 16 chars look like when viewed as 4 longs. I would have
> introduced endianness issues into the program, and that's never a good
> thing -- they tend to spread. [snip]


If CHAR_BIT == 8 and sizeof (long) == 4 (both of which are pretty
likely under the circumstances, and can easily be tested statically),
then unsigned long cannot have a trap representation, and it is easy
to (write code that will) discover just what the representation of
unsigned long is (and also signed long, although signed long might
have one trap representation, which is identifiable by checking the
value of LONG_MIN).

It's probably true that 99 times out of 100 it's better to avoid
using character-type access of other types. Even so, it's better to
know what the Standard actually does require, and to convey that
understanding to other people. Promoting a style of making decisions
out of uncertainty, where there is no need for that uncertainty, is a
bad habit to instill in people.

Jorgen Grahn 04-12-2013 10:53 AM

Re: Writing through an unsigned char pointer
 
On Thu, 2013-04-11, Tim Rentsch wrote:
> Jorgen Grahn <grahn+nntp@snipabacken.se> writes:
>
>> On Thu, 2013-04-11, Noob wrote:
>>> Hello,
>>>
>>> Is the following code valid:
>>>
>>> static void foo(unsigned char *buf)
>>> {
>>> int i;
>>> for (i = 0; i < 16; ++i) buf[i] = i;
>>> }
>>>
>>> void bar(void)
>>> {
>>> unsigned long arr[4];
>>> foo(arr);
>>> }
>>>
>>> The compiler points out that (unsigned long *) is not
>>> compatible with (unsigned char *).
>>>
>>> So I cast to the expected type:
>>>
>>> void bar(void)
>>> {
>>> unsigned long arr[4];
>>> foo((unsigned char *)arr);
>>> }
>>>
>>> I think it is allowed to cast to (unsigned char *)
>>> but I don't remember if it's allowed only to inspect
>>> (read) the values, or also to set them. Also the fact
>>> the "real" type is unsigned means there are no trap
>>> representations, right?

>>
>> Don't know what the language guarantees.
>>
>> Even if I knew about trap representations and stuff, and knew a long
>> is four chars on my target, it would worry me that I have no idea what
>> the 16 chars look like when viewed as 4 longs. I would have
>> introduced endianness issues into the program, and that's never a good
>> thing -- they tend to spread. [snip]

>

....
> It's probably true that 99 times out of 100 it's better to avoid
> using character-type access of other types. Even so, it's better to
> know what the Standard actually does require, and to convey that
> understanding to other people. Promoting a style of making decisions
> out of uncertainty, where there is no need for that uncertainty, is a
> bad habit to instill in people.


Are you saying I promote such a style?

Uncertainty is not the reason I to stay away from weird casts.
But yes, a side effect is that I don't have to waste energy trying
to find out what they mean, in relation to the language and in
relation to my compiler/environment.

I do not want to forbid anyone from discussing casts, trap
representations and UB in this thread. But someone /also/ needed to
point out the obvious: that there are easy, portable and readable
alternatives.

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

Malcolm McLean 04-12-2013 11:11 AM

Re: Writing through an unsigned char pointer
 
On Thursday, April 11, 2013 2:52:22 PM UTC+1, Noob wrote:
>
> Is the following code valid:
>
> static void foo(unsigned char *buf)
> {
> int i;
>
> for (i = 0; i < 16; ++i) buf[i] = i;
> }
>
> void bar(void)
> {
> unsigned long arr[4];
>
> foo(arr);
>
> }
>

It's allowed to cast any block of memory to unsigned chars, and treat it
as sequence of raw bytes or bits. The chars cannot trap, and the compiler
warning is in error, except that void * is a slightly more fiddly
way of achieving the same thing.

However going the other way, from a sequence of bytes to a higher-level
structure like a long, is a bit problematic. On any platform you are likely
to run on, longs will by two's complement. But they might be big endian
or little endian, and occasionally they might not be four bytes.
Technically the sequence 0 1 2 3 could be a trap representation for a
long, but that's so highly unlikely you can ignore the issue.

So you have to know the binary representation of a long. The only portable
way is to copy from one to another.
--
Basic Algorithms. A massive compendium of C routines.
http://www.malcolmmclean.site11.com/www

Noob 04-12-2013 11:30 AM

Re: Writing through an unsigned char pointer
 
Jorgen Grahn wrote:
> On Thu, 2013-04-11, Noob wrote:
>> Hello,
>>
>> Is the following code valid:
>>
>> static void foo(unsigned char *buf)
>> {
>> int i;
>> for (i = 0; i < 16; ++i) buf[i] = i;
>> }
>>
>> void bar(void)
>> {
>> unsigned long arr[4];
>> foo(arr);
>> }
>>
>> The compiler points out that (unsigned long *) is not
>> compatible with (unsigned char *).
>>
>> So I cast to the expected type:
>>
>> void bar(void)
>> {
>> unsigned long arr[4];
>> foo((unsigned char *)arr);
>> }
>>
>> I think it is allowed to cast to (unsigned char *)
>> but I don't remember if it's allowed only to inspect
>> (read) the values, or also to set them. Also the fact
>> the "real" type is unsigned means there are no trap
>> representations, right?

>
> Don't know what the language guarantees.
>
> Even if I knew about trap representations and stuff, and knew a long
> is four chars on my target, it would worry me that I have no idea what
> the 16 chars look like when viewed as 4 longs. I would have
> introduced endianness issues into the program, and that's never a good
> thing -- they tend to spread.
>
> If I were you, at this point I'd sidestep the problem by rewriting the
> code without unusual casts. I don't think I've ever seen a problem
> which could be solved by things like the casting above, but not by
> everyday code without casts. (Ok, except for badly written third-party
> APIs, perhaps.)
>
> You don't show the problem you're trying to solve, so I cannot suggest
> an alternative (except for the obvious and trivial change to bar()).


I'll tell you the whole story, so you can cringe like I did!

What I'm REALLY working with is a 128-bit AES key.

My "sin" is use the knowledge that CHAR_BIT is 8 on the
two platforms I work with, which is why I'm implicitly
using an unsigned char buf[16].

HOWEVER, on one of the two platforms, the geniuses who
implemented the API thought it would be a good idea to
pass the key in an array of 4 uint32_t o_O

Thus what's missing from my stripped-down example is:

extern nasty_API_func(uint32_t *key);

static void foo(unsigned char *buf)
{
int i;
/* not the actual steps to populate buf */
for (i = 0; i < 16; ++i) buf[i] = i;
}

void bar(void)
{
unsigned long key[4];
foo((unsigned char *)key);
nasty_API_func(key);
}

I don't think I have much choice than to cast (or use
an implicit conversion to void *) given the constraints
of the API, do I?

Regards.


James Kuyper 04-12-2013 12:26 PM

Re: Writing through an unsigned char pointer
 
On 04/12/2013 07:30 AM, Noob wrote:
....
> What I'm REALLY working with is a 128-bit AES key.
>
> My "sin" is use the knowledge that CHAR_BIT is 8 on the
> two platforms I work with, which is why I'm implicitly
> using an unsigned char buf[16].


That's not too deadly a sin, it's an accurate assumption on a great many
platforms, but other values do exist: 16 is a popular value on some
DSPs. At least once, somewhere with any of your code that makes that
assumption, you should do something like

#if CHAR_BIT != 8
#error This code requires CHAR_BIT == 8
#endif

> HOWEVER, on one of the two platforms, the geniuses who
> implemented the API thought it would be a good idea to
> pass the key in an array of 4 uint32_t o_O
>
> Thus what's missing from my stripped-down example is:
>
> extern nasty_API_func(uint32_t *key);
>
> static void foo(unsigned char *buf)
> {
> int i;
> /* not the actual steps to populate buf */
> for (i = 0; i < 16; ++i) buf[i] = i;
> }
>
> void bar(void)
> {
> unsigned long key[4];
> foo((unsigned char *)key);
> nasty_API_func(key);
> }
>
> I don't think I have much choice than to cast (or use
> an implicit conversion to void *) given the constraints
> of the API, do I?


Well, at the very least you should change "unsigned long" to uint32_t.

Secondly, you might need to be worried about the endianess of uint32_t.
Your code might set key[0] to 0x1020304 or 0x4030201 (among other
possibilities); do you know which of those two values the
nasty_API_func() should be receiving? If the API is supported only on
platforms with a single endianess, you can get away with building that
knowledge into foo(). However, if API is defined for platforms with
different endianesses, and requires that key[0] have the value
0x1020304, regardless of which endianess uint32_t has, you'll have to
fill in key[] using << and | rather than by accessing it as an array of
char.
--
James Kuyper


All times are GMT. The time now is 04:55 AM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.