Reputation: 5243
I am now taking an ANSI C programming language course and trying to run this code from lecturer's slide:
#include<stdio.h>
int main()
{
int a[5] = {10, 20, 30, 40, 50};
double *p;
for (p = (double*)a; p<(double*)(a+5); ((int*)p)++)
{
printf("%d",*((int*)p));
}
return 0;
}
Unfortunately it doesn't work. On MacOS, XCode, Clang I get an error:"Assignment to cast is illegal, lvalue casts are not supported"
and on Ubuntu gcc I get the next error: "lvalue required as increment operand"
I suspect that issue is compiler as we learn ANCI C and it has its own requirements which can violent other standards.
Upvotes: 3
Views: 1923
Reputation: 222342
((int*)p)++
:The result of (int *) p
is a value. This is different from an lvalue. An lvalue (potentially) designates an object. For example, after int x = 3;
, the name x
designates the object that we defined. We can use it in an expression generally, such as y = 2*x
, and then the x
is used for its value. But we can also use it in an assignment, such as x = 5
, and then the x
is used for the object.
The expression (int *) p
takes p
and converts it to a pointer to int
. The result is only a value. It is not an lvalue, so it does not represent an object that can be modified.
The ++
operator modifies an object. So it can only be applied to an lvalue. Since (int *) p
is not an lvalue, ++
cannot be applied to it.
The code from the slide, as you have shown it, is incorrect, and I would not expect it to work in any C implementation. (C does permit implementations to make many extensions, but extending C to permit this operation would be unusual.)
(double*)a
and (int*)p
:C does permit you to convert pointers to objects to pointers to different kinds of objects. There are various rules about this. An important one is that the resulting pointer must have the correct alignment for the type it points to.
Objects have various alignment requirements, meaning they must be placed at particular addresses in memory. Commonly, char
objects may have any address, int
objects must be at multiples of four bytes, and double
objects must be at multiples of eight bytes. These requirements vary from C implementation to C implementation. I will use these values for illustration.
When you convert a proper double *
to an int *
, we know that the resulting pointer is a multiple of four, because it started as a multiple of eight (assuming the requirements stated above). So that is a safe conversion. When you convert an int *
, to a double *
, it may have the wrong alignment. In particular, given an array a
of int
, we know that either a[0]
or a[1]
must be improperly aligned for a double
, because, if one of them is at a multiple of eight bytes, the other one must be off by four bytes from a multiple of eight. Therefore, the conversions from int *
to double *
in this code are not defined by the C standard.
They might work in many C implementations, but you should not rely on them.
The C rules also state that when a pointer to an object is converted back to its original type, the result is equal to the original pointer. So, the round-trip conversions in the sample code would be okay if the alignment rules had been obeyed: You can convert an int *
to a double *
and back to an int *
, provided alignment requirements are obeyed.
If the double *
had been used to access a double
, that would violate aliasing rules. Generally, C does not define the behavior when an object of one type is accessed as if it were another type. There are some exceptions, notably with character types. However, simply converting pointers back and forth without using them to access objects is okay, except for the alignment problem.
Upvotes: 5