Typecasting pointers

N

Nishu

Hi All,

Is it valid in C to typecast a pointer?

eg. code snippet... considering int as 16 bit and long as 32 bit.

int *variable, value;

*((long*)variable)++ = value;
*((long*)variable)++ = value;
*((long*)variable)++ = value;
*((long*)variable)++ = value;

Thanks,
Nishu
 
G

Guest

Nishu said:
Hi All,

Is it valid in C to typecast a pointer?

If alignment requirements are met, then yes. However, with a few
exceptions, all you are allowed to do with the result is convert it
back to the original type.
eg. code snippet... considering int as 16 bit and long as 32 bit.

int *variable, value;

*((long*)variable)++ = value;
*((long*)variable)++ = value;
*((long*)variable)++ = value;
*((long*)variable)++ = value;

This is not allowed. Even when you are able to convert 'variable' to a
pointer-to-long, you may not use this pointer to access anything that
isn't really a long.
 
N

Nishu

exceptions, all you are allowed to do with the result is convert it
back to the original type.



pointer-to-long, you may not use this pointer to access anything that
isn't really a long.

In case value is long...
long value;

I'm getting warnings on my compiler.."objects that have been cast are
not l-value."

Thanks,
Nishu
 
B

Ben Bacarisse

Nishu said:
In case value is long...
long value;

I'm getting warnings on my compiler.."objects that have been cast are
not l-value."

Take out the UB caused by the cast to long * by replacing it with int *
and you should get the same message. It describes exactly and
succinctly what is wrong. The result of a cast is not an lvalue -- it
never denotes an object that can be changed. In your case,

*((int *)variable)++

would try to increment something, but the result of a cast is never a
thing that can be incremented, no matter what the type is used in the
cast. You should get the message from the more obvious:

int x;
((int)x)++; /* error... casts do not make lvalues */
 
R

Richard Heathfield

Nishu said:
Hi All,

Is it valid in C to typecast a pointer?

It's rarely wise, and often not valid.
eg. code snippet... considering int as 16 bit and long as 32 bit.

int *variable, value;

*((long*)variable)++ = value;

Let's count the problems.

Firstly, value is indeterminate, so you can't use its value legitimately.
The behaviour is undefined if you try.

Secondly, even if that weren't a problem, variable is indeterminate, so you
can't use its value legitimately. The behaviour is undefined if you try.

Thirdly, even if those weren't problems, variable is an int *, and (if its
value isn't indeterminate or a null pointer) it points to an int object,
which may not be aligned correctly for a long int.

Fourthly, even if those weren't problems, a cast-expression such as
(long *)variable yields a value, not an object, and you can't use ++ on a
mere value. It requires an object.

Sort out those problems and then ask again.
 
N

Nishu

Nishu said:






Sort out those problems and then ask again.

Hi,
I want to cast my 16bit pointer as 32 bit pointer for copying
operation, after that i need 16bit pointer operations only. here's the
test version of what I need to do..

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
unsigned short value;
long lvalue;

short *ptr, *ptr1;

ptr = (short*) malloc(sizeof(short) * 8);

ptr1 = ptr;
value = 0xFFF0;

lvalue = (unsigned short)value | ((unsigned short)value << 16);

*((long*)ptr)++ = lvalue; /* is it portable? */
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;

free (ptr1);
return 0;
}


MSVC doesn't give error here, but I'm doubtful about its portability.
Please help me about other possible loop holes too.

Thanks,
Nishu
 
N

Nishu

Hi,
I want to cast my 16bit pointer as 32 bit pointer for copying
operation, after that i need 16bit pointer operations only. here's the
test version of what I need to do..

here's lil' correction... and one more doubt.

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
short value; /* it is signed actually*/
long lvalue;

short *ptr, *ptr1;

ptr = malloc(sizeof(short) * 8);

ptr1 = ptr;
value = 0xFFF0; /* i get warning here. I don't understand why.
warning is
'=' : truncation from 'const int ' to 'short' */

lvalue = (unsigned short)value | ((unsigned short)value << 16);

*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;

free (ptr1);
return 0;
}

Thanks,
Nishu
 
C

Chris Dollin

/Why/ do you want to do this bizarre thing?
here's lil' correction... and one more doubt.

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
short value; /* it is signed actually*/
long lvalue;

short *ptr, *ptr1;

ptr = malloc(sizeof(short) * 8);

Better is likely to be:

ptr = malloc( 8 * sizeof( *ptr ) );

If you want 8 things. (Below, it looks like you might really
want 4).
ptr1 = ptr;
value = 0xFFF0; /* i get warning here. I don't understand why.
warning is
'=' : truncation from 'const int ' to 'short' */

Well, yes. You say your `short`s are 16 bits. 0xFFF0 is a positive
value (there are no negative literals),and it's an `int`. It won't fit
into a signed short. Your compiler is warning you that you're trying
to stuff an `int` into a `short` and that you may well have lost
information. (Opinions about the values of such a message vary.)
lvalue = (unsigned short)value | ((unsigned short)value << 16);

Why not declare `value` as an `unsigned short`, since that's all you
ever use it as? Come to that, why not assign a literal directly to
`lvalue` and not bother with `value` at all?
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;

Similarly, if what you want to do is assign to successive `long`s
starting at the mallocated address, why not do the obvious, which
is

long *ptr = malloc( 4 * sizeof( *ptr ) );
...
*ptr++ = lvalue;
*ptr++ = lvalue;
*ptr++ = lvalue;
*ptr++ = lvalue;

or even (having declared `int i`):

for (i = 0; i < 4; i += 1) ptr = lvalue;
free (ptr1);
return 0;
}

I don't think you're telling us everything you need to.
 
K

Kelly

Nishu said:
here's lil' correction... and one more doubt.

#include<stdio.h>
#include<stdlib.h>

int main(void)
{
short value; /* it is signed actually*/
long lvalue;

short *ptr, *ptr1;

ptr = malloc(sizeof(short) * 8);

ptr1 = ptr;
value = 0xFFF0; /* i get warning here. I don't understand why.
warning is
'=' : truncation from 'const int ' to 'short' */

lvalue = (unsigned short)value | ((unsigned short)value << 16);

*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;

free (ptr1);
return 0;
}

Thanks,
Nishu
1>------ Build started: Project: Test, Configuration: Debug Win32 ------
1>Compiling...
1>test.c
1>c:\visual studio 2005\projects\test\test\test.c(43) : warning C4213:
nonstandard extension used : cast on l-value
1>c:\visual studio 2005\projects\test\test\test.c(44) : warning C4213:
nonstandard extension used : cast on l-value
1>c:\visual studio 2005\projects\test\test\test.c(45) : warning C4213:
nonstandard extension used : cast on l-value
1>c:\visual studio 2005\projects\test\test\test.c(46) : warning C4213:
nonstandard extension used : cast on l-value
1>c:\visual studio 2005\projects\test\test\test.c(43) : *warning C6011:
Dereferencing NULL pointer '((long *)ptr)++': Lines: 29, 30, 32, 34, 36,
37, 41, 43*

Looks ominous to me!

1>Linking...
1>Embedding manifest...
1>Test - 0 error(s), 5 warning(s)
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
 
K

Keith Thompson

Nishu said:
here's lil' correction... and one more doubt.

Ok, I think I see what you're trying to do. You want to create an
array of shorts, and initialize them all to the same value. If it
were an array of bytes memset() would be just the thing, but there's
no standard routine that does for shorts what memset() does for bytes.

You're assuming that short is 16 bits and long is 32 bits. Be aware
that these assumptions are *not* portable. If you're willing to live
with the fact that your code will break, perhaps quietly, on some
systems, you should at least document your assumptions.
#include<stdio.h>
#include<stdlib.h>

int main(void)
{
short value; /* it is signed actually*/

Why is it signed? For what you're doing, using unsigned types would
be simpler -- ahd the value 0xFFF0 won't fit into a 16-bit unsigned
object.
long lvalue;

"lvalue" turns out to be a poor name for this variable. I understand
that you meant it to be an abbreviation for "long value", but in C the
term "lvalue" has a specific meaning (roughly, it's an expression that
designates an object).
short *ptr, *ptr1;

Again, unsigned short would probably serve your purposes better.
ptr = malloc(sizeof(short) * 8);

Thank you for not casting the result of malloc(), but an even better
way to write this is:

ptr = malloc(8 * sizeof *ptr);

And you should *always* check the result of malloc().
ptr1 = ptr;

You're saving the value of ptr so you can free() it later. That's
good, but see below for a comment on this.
value = 0xFFF0; /* i get warning here. I don't understand why.
warning is
'=' : truncation from 'const int ' to 'short' */

The maximum value of signed short on your system is 0x7FFF; that's
what your compiler is warning you about.
lvalue = (unsigned short)value | ((unsigned short)value << 16);

If you declare value as an unsigned short, you don't need the casts.
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;
*((long*)ptr)++ = lvalue;

This is the core of what you're doing, and of your problem.

You're initializing an array of 2-byte integers, but you're trying to
do it 4 bytes at a time, presumably because you think it will be
faster (more on that later). In general, this is a dangerous thing to
do, because there's no guarantee that an array of shorts will be
properly aligned as an array of longs. In this case, you happen to be
ok, because malloc() returns a result suitably aligned for any type.
If you're thinking of using this as a general technique, alignment
will eventually come back and bite you. You should also think about
the case where the length of the array is odd.

The "++" operator modifies an object. It can't be applied to
something that isn't an object -- specifically, it requires an lvalue,
an expression that designates an object. A cast yields a converted
*value*; it doesn't refer to any object. So applying "++" to the
result of a cast makes no more sense than assigning to the result of
"+":

int x;
(x + 2) = 4; /* illegal, (x + 2) is not an lvalue */

(The language *could* conceivably have defined reasonable semantics
for treating the result of a cast as an lvalue, but it didn't, and
we're stuck with that.)

A pointer increment advances the pointer by one object size,
specifically the size of the object to which it points. You want to
advance ptr by the size of a long, but it's declared to point to a
short. Casting the pointer *value* and incrementing it is, as
discussed above, illegal.

What you really want to do is take the value of ptr (of type short*),
convert it to long*, add 1 to the resulting *value*, and convert the
result back to short*:
ptr = (short*)((long*)ptr + 1);
Or you can just use the knowledge (well, assumption) that a long is
twice the size of a short, and simply do this:
ptr += 2;

Now this isn't something you can easily do as a side effect of your
assignment statement, but so what? Extreme terseness isn't always a
virtue. Do the assignment, then increment the pointer:

*((long*)ptr) = lvalue;
ptr += 2;
*((long*)ptr) = lvalue;
ptr += 2;
*((long*)ptr) = lvalue;
ptr += 2;
*((long*)ptr) = lvalue;

And we've dropped the final increment, since we're not using the value
of ptr again.
free (ptr1);
return 0;
}

An outline of what you've done with memory allocation is:

ptr = malloc(...);
ptr1 = ptr;
/* manipulate ptr */
free(ptr1);

This is perfectly correct, but just as a matter of style you might
consider manipulating the *copy* of ptr rather than ptr itself. This
makes for greater symmetry (ptr = malloc(...); ... free(ptr);). You
can even declare ptr as const to make (reasonably) sure you don't
accidentally clobber it.

Now let's zoom back and look at what you're trying to do.

Except in performance-critical code, it's usually best to write the
most straightforward possible code, and let the compiler optimize it
as well as it can. In your program, you write a considerable amount
of extra code to initialize your array 4 bytes at a time rather than 2
bytes at a time, and you've unrolled the initialization loop (writing
four separate assignments rather than a single one in a loop). Either
or both of these *might* make your code a little faster -- or they
might not. By making your code more complex, you may have made it
more difficult for the optimizer to analyze; conceivably a good
optimizing compiler could have done a better job than you have. And,
by trying to be fancy, you've gotten the code wrong, which has cost
you many orders of magnitude more of your own time than the CPU time
you might have saved.

Once you get your code working, try measuring the *actual* time spent;
you may find that the improvement is either nonexistent, or just not
worth the effort.

Or you just might find that it's significant, that the code is in an
inner loop in a performance-critical application, and that this was
all worth it. Or it could be worthwhile just as a learning
experience.

If you do want to do this optimization, there are easier ways to go
about it. You can treat the whole array as an array of unsigned longs
and initialize it that way, rather than converting pointers on each
iteration.

Here's a simplified version of your program. I've removed the
optimizations, but I've added some error checking.
================================
#include <stdio.h> /* This isn't actually used */
#include <stdlib.h>
int main(void)
{
#define COUNT 8
#define INIT 0xFFF0

unsigned short value;
unsigned short *const ptr = malloc(COUNT * sizeof *ptr);
int i;

if (ptr == NULL) {
fprintf(stderr, "malloc() failed\n");
exit(EXIT_FAILURE);
}

for (i = 0; i < COUNT; i ++) {
ptr = INIT;
}

free (ptr);
return 0;
}
================================

And here's another version with optimization. I've unrolled the loop
as you did, and I've used a pointer rather than an integer index to
access the array. (It's tempting to assume that pointer accesses will
be faster than indexing, but it's not necesarily the case -- and an
optimizing compiler can often tranform one to the other.)

One note here. I've defined COUNT as a macro rather than using the
"magic number" 8. The idea is that you can change the definition of
COUNT if necessary without touching the rest of the program. For the
unoptimized version above, that works. For the optimized version
below, it doesn't; the number of assignment statements needs to be
COUNT/2 (and COUNT needs to be even), but if you change the definition
of COUNT you also need to manually update the unrolled loop.

================================
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(void)
{
/*
* We require long to be twice the size of short.
* We'll check this with an assert(); if it's not the case,
* we don't even want the program to run.
*/

#define COUNT 8
#define INIT 0xFFF0
#define INIT_TWICE (((unsigned long)INIT << 16) | (unsigned long)INIT)

unsigned short value;
unsigned short *const ptr = malloc(COUNT * sizeof *ptr);
unsigned long *lptr;

assert(sizeof(short) * 2 == sizeof(long));

if (ptr == NULL) {
fprintf(stderr, "malloc() failed\n");
exit(EXIT_FAILURE);
}

lptr = (unsigned long*)ptr;
*lptr++ = INIT_TWICE;
*lptr++ = INIT_TWICE;
*lptr++ = INIT_TWICE;
*lptr = INIT_TWICE;

free (ptr);
return 0;
}
================================
 
D

David T. Ashley

Nishu said:
Hi All,

Is it valid in C to typecast a pointer?

eg. code snippet... considering int as 16 bit and long as 32 bit.

int *variable, value;

*((long*)variable)++ = value;
*((long*)variable)++ = value;
*((long*)variable)++ = value;

In type-casting a pointer, all you do is change the way the compiler treats
it in two contexts:

a)Array indexing and incrementing/decrementing (the compiler has to know how
large the data pointed to is in order to know how to index or advance a
pointer).

b)Variable/structure references using the pointer.

Where program space is critical (small embedded work), one finds all sorts
of tricks with unions and pointers to get the compiler to behave in the
desired way.

But in a larger system (such as an x86 box), there really aren't any
contexts where one needs to do this. If you have to go that low ... better
to write certain modules in assembly-language than to abuse the compiler in
ways which may become invalid with a compiler version or platform change.
 
W

Walter Roberson

David T. Ashley said:
In type-casting a pointer, all you do is change the way the compiler treats
it in two contexts:
a)Array indexing and incrementing/decrementing (the compiler has to know how
large the data pointed to is in order to know how to index or advance a
pointer).
b)Variable/structure references using the pointer.

If the pointer is cast to a type with a stricter alignment requirement
and then cast back again, the twice-cast pointer need not be valid.
(However, if a pointer is cast to a type with the same strictness or
less strict alignment, it may be cast back and is promised to compare
equal to the original.)

We can see from this that the actual representation can
irreversibly change when a pointer is cast to another type.
There can also be changes to such things as base segment and
offset when a pointer is cast -- some systems allow multiple
representations for the same pointer.

All in all, it can get more complicated internally than you imply
by saying that "all you do is change the way the compiler treats
it in two contexts."
 
K

Keith Thompson

David T. Ashley said:
In type-casting a pointer, all you do is change the way the compiler treats
it in two contexts:

a)Array indexing and incrementing/decrementing (the compiler has to know how
large the data pointed to is in order to know how to index or advance a
pointer).

b)Variable/structure references using the pointer.
[...]

Casting (not "type-casting") a pointer converts its value to some
specified type, yielding a value of the new type. For a
pointer-to-pointer conversion, the result may or may not be a valid,
depending on a number of factors, some defined by the standard, some
implementation-defined.

For example, converting an object pointer to type unsigned char*
always gives you a valid result (assuming the original value is valid)
and allows you to access the representation of the pointed-to object.
 
P

pete

Walter Roberson wrote:
If the pointer is cast to a type with a stricter alignment requirement
and then cast back again, the twice-cast pointer need not be valid.

The first cast pointer need not be valid either.

char array[sizeof(int) + 1];

*(int *)(array + 1) = 0;
 
D

David T. Ashley

Walter Roberson said:
(However, if a pointer is cast to a type with the same strictness or
less strict alignment, it may be cast back and is promised to compare
equal to the original.)

Is that a personal observation or part of a standard?
 
W

Walter Roberson

Is that a personal observation or part of a standard?

ANSI X.3-159

3.3.4 Cast Operators
[...]
A pointer to an object or incomplete type may be converted to
a pointer to a different object type or a different incomplete
type. The resulting pointer might not be valid if it is
improperly aligned for the type pointed to. It is guaranteed,
however, that a pointer to an object of a given alignment
may be converted to a pointer to an object of the same
alignment or a less strict alignment and back again; the result
shall compare equal to the original pointer. (An object
that has character type has the least strict alignment.)
 
D

David T. Ashley

Walter Roberson said:
Is that a personal observation or part of a standard?

ANSI X.3-159

3.3.4 Cast Operators
[...]
A pointer to an object or incomplete type may be converted to
a pointer to a different object type or a different incomplete
type. The resulting pointer might not be valid if it is
improperly aligned for the type pointed to. It is guaranteed,
however, that a pointer to an object of a given alignment
may be converted to a pointer to an object of the same
alignment or a less strict alignment and back again; the result
shall compare equal to the original pointer. (An object
that has character type has the least strict alignment.)

Interesting.

I've never seen a machine where simply casting a pointer to a more strict
alignment will cause it to change its value. The trouble usually begins
when you try to USE the casted pointer.

But interesting that the standard only makes guarantees in one direction.
 
N

Nishu

And here's another version with optimization. I've unrolled the loop
as you did, and I've used a pointer rather than an integer index to
access the array. (It's tempting to assume that pointer accesses will
be faster than indexing, but it's not necesarily the case -- and an
optimizing compiler can often tranform one to the other.)

One note here. I've defined COUNT as a macro rather than using the
"magic number" 8. The idea is that you can change the definition of
COUNT if necessary without touching the rest of the program. For the
unoptimized version above, that works. For the optimized version
below, it doesn't; the number of assignment statements needs to be
COUNT/2 (and COUNT needs to be even), but if you change the definition
of COUNT you also need to manually update the unrolled loop.

================================
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(void)
{
/*
* We require long to be twice the size of short.
* We'll check this with an assert(); if it's not the case,
* we don't even want the program to run.
*/

#define COUNT 8
#define INIT 0xFFF0
#define INIT_TWICE (((unsigned long)INIT << 16) | (unsigned long)INIT)

unsigned short value;
unsigned short *const ptr = malloc(COUNT * sizeof *ptr);
unsigned long *lptr;

assert(sizeof(short) * 2 == sizeof(long));

if (ptr == NULL) {
fprintf(stderr, "malloc() failed\n");
exit(EXIT_FAILURE);
}

lptr = (unsigned long*)ptr;
*lptr++ = INIT_TWICE;
*lptr++ = INIT_TWICE;
*lptr++ = INIT_TWICE;
*lptr = INIT_TWICE;

free (ptr);
return 0;}

Thank you Keith and all. I'll keep these things in mind.

Thanks,
Nishu
 
W

Walter Roberson

I've never seen a machine where simply casting a pointer to a more strict
alignment will cause it to change its value. The trouble usually begins
when you try to USE the casted pointer.

Suppose you are using a machine which has int pointers distinct
from char pointers -- e.g., (int *)0x00a5 refers to the 166'th
int location and (char *)0x00a5 refers to the 166'th char location.
Suppose further that sizeof(int) == 4 and that the memory fields
overlap, so (int *)0x00a5 is the same memory location as
(char *)(0x00a5*sizeof(int)) = (char *)0x0294. [According to some
previous postings, similar machines exist and are actually used.]

In this situation, to convert an int* to the less strict alignment, you
multiply the int* address by sizeof(int) to get the equivilent (char *)
address. If one then convers that (char *) back to (int *), divide the
(char *) memory address by sizeof(int) and (int *)0x00a5 is recovered.

Now try that the other way around, converting (char *)0x00a5 to
an (int *). Divide the (char *) memory address by sizeof(int)
to get (int *)0x0029; cast back by multiplying by sizeof(int)
to get (char *)0x00a4 . Oh-oh, that's not the original (char *) address!

Hence, casting a pointer can change its value, and casting to
a stricter alignment and back can result in something that doesn't
point to the original location.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top