Pointer conversions

I

Ioannis Vranos

Are the following guaranteed to work always as *C90* code?

1.

#include <stdio.h>


void some_func(int *p, const size_t SIZE)
{
size_t i;

for(i=0; i<SIZE; ++i)
printf("%d ", p);
}


int main(void)
{
int array[10][5]= {0};

some_func(array[0], sizeof(array)/sizeof(**array));

puts("");

return 0;
}



The above prints 50 zeros. I think it is guaranteed to work, since all
arrays are sequences of their elements.



2.

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


int main(void)
{
size_t i, j;

int array[50]= {0};

int (*p)[5]= (int (*)[5])(&array[0]);

for (i= 0; i< 10; ++i)
for(j=0; j<5; ++j)
printf("%d ", p[j]);

puts("");

return 0;
}


Here p behaves as a 2-dimensional matrix, that is a 10x5 matrix. I think
it is guaranteed to work for the same reason as the first one, that is
we can treat an array (sequence) of integers as various types of integer
arrays.
 
P

pete

Ioannis said:
Are the following guaranteed to work always as *C90* code?

1.

#include <stdio.h>

void some_func(int *p, const size_t SIZE)
{
size_t i;

for(i=0; i<SIZE; ++i)
printf("%d ", p);
}

int main(void)
{
int array[10][5]= {0};

some_func(array[0], sizeof(array)/sizeof(**array));

puts("");

return 0;
}

The above prints 50 zeros. I think it is guaranteed to work, since all
arrays are sequences of their elements.


It is not guaranteed to work.
The problem is that there is no array of int
with more than 5 members declared anywhere.

Pointers to char are allowed to step through the bytes
of any object, but that's by a special rule.

I can't conceive of any mechanism by which your code could fail,
but the guarantee of which you speak, is not there.

Your pointer to int,
is over running the boundaries of an array of 5 int.
 
P

Pierre Asselin

Ioannis Vranos said:
Are the following guaranteed to work always as *C90* code?
[ ... ]
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
int array[50]= {0};
int (*p)[5]= (int (*)[5])(&array[0]);
Here p behaves as a 2-dimensional matrix, that is a 10x5 matrix. [ ... ]

It's not necessarily legal, but it ought to be. There is a related
example at the end of 6.7.5.3 in the C99 Rationale (non-normative of
course),

----------------------------------------------------------------------
The following example demonstrates how to declare parameters
in any order and avoid lexical ordering issues

void g(double *ap, int n)
{
double (*a)[n]= (double (*)[n]) ap;
/* ... */ a[1][2] /* ... */
}

in this case, the parameter ap is assigned to a local
pointer that is declared to be a pointer to a variable
length array. The function g can be called as in

{
double x[10][10];
g(&x[0][0], 10);
}
----------------------------------------------------------------------

This sort of thing is common in numerical software and (some) authors
of the standard intended it to work, but the normative standard doesn't
necessarily guarantee it. Personally I would consider any failing
implementation as broken.
 
A

Army1987

Ioannis said:
Are the following guaranteed to work always as *C90* code?
[ Treating an int array[10][5] as it were int array[1][50] ]
That's one of those things for which the standard defines no behavior,
without any reason not to do define one. I'd be very surprised if it
failed, but that would not make an implementation non-conforming. Unless
there is some implementation on which that fails, I'd not object to it
being allowed.
(Another such thing is the struct hack, or printing a nonnegative signed
int with %x. They can fail, but I can't see any way in which they could,
other than the implementation being *intentionally* hostile.)
 
H

Harald van Dijk

Ioannis said:
Are the following guaranteed to work always as *C90* code?
[ Treating an int array[10][5] as it were int array[1][50] ] That's one
of those things for which the standard defines no behavior, without any
reason not to do define one. I'd be very surprised if it failed, but
that would not make an implementation non-conforming. Unless there is
some implementation on which that fails, I'd not object to it being
allowed.

As I recall, it does fail on some implementations, because the optimiser
doesn't realise it's possible for p1[10][3] and p2[5][8] to overlap (and
in fact it isn't possible in standard C), so code such as

p1[10][3] = 5;
p2[5][8] = 3;
return p1[10][3];

would return 5 without re-reading p1[10][3]. However, I cannot come up
with a concrete example showing the behaviour right now. It was with one
version of GCC, but the one I'm using now appears to handle array
accesses differently.
 
C

cr88192

Army1987 said:
Ioannis said:
Are the following guaranteed to work always as *C90* code?
[ Treating an int array[10][5] as it were int array[1][50] ]
That's one of those things for which the standard defines no behavior,
without any reason not to do define one. I'd be very surprised if it
failed, but that would not make an implementation non-conforming. Unless
there is some implementation on which that fails, I'd not object to it
being allowed.
(Another such thing is the struct hack, or printing a nonnegative signed
int with %x. They can fail, but I can't see any way in which they could,
other than the implementation being *intentionally* hostile.)

I have an example:
consider the C implementation is built on top of a VM framework of some
sort;
this VM does not allow multidimensional arrays as such, but treats the case
of multidimensional arrays as a nested set of 1D arrays.

all works well, as long as we use the array as specified.
going outside this, the implementation may well crash, issue bounds-check
exceptions, ...

so, in general, I would personally probably advise against relying on this
kind of behavior (instead, when needed, faking multidimensional arrays using
larger 1D arrays, so that the multidimensional case need not be compromised
as such).

in my case, for my compiler I had actually been fairly tempted originally to
implement multidimensional arrays like the previous, but decided against it
(my main reason though, was more that it would have made gcc integration
more problematic).


meanwhile, I am considering an idea for adding lexical closures to my
compiler (inner functions + a __lambda keyword).

this would be pulled off, in this case, by allocating both the function
stub, and captured bindings, in an area of "non-reclaimable" memory. as
such, it would only be possible to make a finite (though possibly still
reasonably large) number of them during the operation of the program.

otherwise, they would be made to look and behave just like ordinary function
pointers.

 
P

Pierre Asselin

cr88192 said:
Army1987 said:
Ioannis said:
Are the following guaranteed to work always as *C90* code?
[ Treating an int array[10][5] as it were int array[1][50] ]
[ ... ] Unless
there is some implementation on which that fails, I'd not object to it
being allowed.
I have an example:
consider the C implementation is built on top of a VM framework of some
sort;
this VM does not allow multidimensional arrays as such, but treats the case
of multidimensional arrays as a nested set of 1D arrays.

I don't see how that affects C. The OP's int array[10][5] has to
be laid out in memory the same way as an int [50]. If the VM or hardware
has some weird facility, the C implementation is not obligated to use it.
all works well, as long as we use the array as specified.
going outside this, the implementation may well crash, issue bounds-check
exceptions, ...

It looks like a conforming implementation could stuff bounds
information into fat pointers and trap at runtime. That could be
tolerable to me as an extension, but if the behavior can't be switched
off the an implementation would be disqualified as far as I am
concerned.

so, in general, I would personally probably advise against relying on this
kind of behavior (instead, when needed, faking multidimensional arrays using
larger 1D arrays, so that the multidimensional case need not be compromised
as such).

Yeah, if you like ugly code. These days I deal with arrays of 3n floats;
in some parts of the code it is more convenient to view them as nx3 arrays,
in others it is more convenient to treat them as flat arrays. The
ability to do

float (*ptr)[3]= flat_ptr;

improves readability and maintainability --a lot. In C99 we now have

int m= blabla;
float (* restrict matrix)[m]= flat_ptr;

as in the non-normative example I quoted, and it's about time too.
 
C

cr88192

Pierre Asselin said:
cr88192 said:
Army1987 said:
Ioannis Vranos wrote:

Are the following guaranteed to work always as *C90* code?
[ Treating an int array[10][5] as it were int array[1][50] ]
[ ... ] Unless
there is some implementation on which that fails, I'd not object to it
being allowed.
I have an example:
consider the C implementation is built on top of a VM framework of some
sort;
this VM does not allow multidimensional arrays as such, but treats the
case
of multidimensional arrays as a nested set of 1D arrays.

I don't see how that affects C. The OP's int array[10][5] has to
be laid out in memory the same way as an int [50]. If the VM or hardware
has some weird facility, the C implementation is not obligated to use it.

possibly, but say the C app implementation is expected to share data with
the other languages that operate on the same VM.

anyways, that it be laid out the same as int[50] is not actually required as
such, only that this is the common practice...

It looks like a conforming implementation could stuff bounds
information into fat pointers and trap at runtime. That could be
tolerable to me as an extension, but if the behavior can't be switched
off the an implementation would be disqualified as far as I am
concerned.

on what grounds exactly?
that is doesn't allow one to, technically, violate the standard?...

so, in general, I would personally probably advise against relying on
this
kind of behavior (instead, when needed, faking multidimensional arrays
using
larger 1D arrays, so that the multidimensional case need not be
compromised
as such).

Yeah, if you like ugly code. These days I deal with arrays of 3n floats;
in some parts of the code it is more convenient to view them as nx3
arrays,
in others it is more convenient to treat them as flat arrays. The
ability to do

float (*ptr)[3]= flat_ptr;

improves readability and maintainability --a lot. In C99 we now have

int m= blabla;
float (* restrict matrix)[m]= flat_ptr;

as in the non-normative example I quoted, and it's about time too.

I perfer to stick with the approach of emulating the multidimensional arrays
via a large 1D array.
it is not that bad IMO:

float *ma;
ma=malloc(16*sizeof(float));
memset(ma, 0, 16*sizeof(float));

ma[0*4+0]=1;
ma[1*4+1]=1;
ma[2*4+2]=1;
ma[3*4+3]=1;

this convention extends nicely and easily to a higher number of dimensionas
as well.
 
K

Keith Thompson

cr88192 said:
I don't see how that affects C. The OP's int array[10][5] has to
be laid out in memory the same way as an int [50]. If the VM or hardware
has some weird facility, the C implementation is not obligated to use it.

possibly, but say the C app implementation is expected to share data with
the other languages that operate on the same VM.

anyways, that it be laid out the same as int[50] is not actually
required as such, only that this is the common practice...
[...]

It's not *directly* required, but I believe it's indirectly required.
Array elements must be allocated consecutively. I don't believe it's
possible for an implementation to lay out
int array_2d[10][5];
and
int array_1d[50];
differently.

But there's not a lot that a program can portably do to take advantage
of that, other than by access both as arrays of unsigned char. Though
array_2d[1][0] and array_2d[0][5] will *usually* refer to the same int
object, the latter, strictly speaking, invokes undefined behavior.
This can cause problems either if the implementation performs
aggressive bounds checking, or if an optimizing compiler generates
"bad" code because it assumes that all array references will be within
declared bounds.
 
P

Pierre Asselin

Keith Thompson said:
cr88192 said:
anyways, that it be laid out the same as int[50] is not actually
required as such, only that this is the common practice... [...]

It's not *directly* required, but I believe it's indirectly required.
Array elements must be allocated consecutively. I don't believe it's
possible for an implementation to lay out
int array_2d[10][5];
and
int array_1d[50];
differently.

That was my impression as well.
But there's not a lot that a program can portably do to take advantage
of that, other than by access both as arrays of unsigned char. Though
array_2d[1][0] and array_2d[0][5] will *usually* refer to the same int
object, the latter, strictly speaking, invokes undefined behavior.

But the OP wasn't breaking that rule, he only referred to array_2d[j]
with j<5. None of his uses had any aliasing. The question was
whether it is legal to convert a flat array to a 2d view,

int flat[50];
int (*array_2d)[5]= (int (*)[5]) flat;

and proceed as-if

int array_2d[10][5];

He was also asking about the reverse conversion,

int array_2d[10][5];
int *flat= array_2d[0];
for(i= 0; i<50; i++) {/* use flat */}

and *that* I think is technically illegal, but it's a damn shame.
 
P

Pierre Asselin

cr88192 said:
Pierre Asselin said:
I have an example:
consider the C implementation is built on top of a VM framework of some
sort;
this VM does not allow multidimensional arrays as such, but treats the
case
of multidimensional arrays as a nested set of 1D arrays.

I don't see how that affects C. The OP's int array[10][5] has to
be laid out in memory the same way as an int [50]. If the VM or hardware
has some weird facility, the C implementation is not obligated to use it.
possibly, but say the C app implementation is expected to share data with
the other languages that operate on the same VM.

It can provide extensions to do so, but not pollute the core language.

[ ... ] but if the behavior can't be switched
off the an implementation would be disqualified as far as I am
concerned.
on what grounds exactly?
that is doesn't allow one to, technically, violate the standard?...

On the grounds that it would take a heads-in-the-clouds, ivory-tower,
weasel-wording, loophole-mucking language lawyer with no interest
in practical applications to come up with an implementation like
that. Doesn't fit my tastes, but that's just me.

float (*ptr)[3]= flat_ptr;

improves readability and maintainability --a lot. [ ... ]
I perfer to stick with the approach of emulating the multidimensional arrays
via a large 1D array.
it is not that bad IMO:
float *ma;
ma=malloc(16*sizeof(float));
memset(ma, 0, 16*sizeof(float));
ma[0*4+0]=1;
ma[1*4+1]=1;
ma[2*4+2]=1;
ma[3*4+3]=1;

/* Okay, */
float *flat= malloc(m*n*p*sizeof(flat[0]));
float (*tensor)[n][p]= (void *) flat;

tensor[j][k]; /* cool */
flat[i*n*p+j*p+k]; /* ugly */
flat[i*n*p + j*p + k]; /* ugly */
flat[(i*n+j)*p+k];; /* ugly */
flat[(i*n + j)*p + k];; /* ugly */
#define INDEX(i,j,k) ((i)*n*p+(j)*p+(k))
flat[INDEX(i,j,k)] /* desperate */

The uglies stay ugly and error-prone no matter how you tweak the
whitespace. Your choice.

There is also an efficiency issue. With the flat array notation
the compiler has to be damn good at dependency analysis and strength
reduction to optimize loops over flat[]. With the C99 VLA (and
restrict, which I left out above) the type system supplies the
necessary information about tens[].
 
T

Thad Smith

Army1987 said:
Ioannis said:
Are the following guaranteed to work always as *C90* code?
[ Treating an int array[10][5] as it were int array[1][50] ]
That's one of those things for which the standard defines no behavior,
without any reason not to do define one. I'd be very surprised if it
failed, but that would not make an implementation non-conforming. Unless
there is some implementation on which that fails, I'd not object to it
being allowed.
(Another such thing is the struct hack, or printing a nonnegative signed
int with %x. They can fail, but I can't see any way in which they could,
other than the implementation being *intentionally* hostile.)

Using the struct hack, say we define
struct s {
int cost
char name[1];
};

struct s* ps;
int i;
char c;

and dynamically allocate structs with added length for the required name
length. Now access an entry with
c = ps->name;

On an 8-bit target, it is more efficient to do 8-bit arithmetic than
multibyte arithmetic. The compiler can see that the maximum valid index of
name is 0, so needn't bother with looking at the upper bytes of i to
compute the effective address. I suppose with an upper index of 0, it
doesn't even need to bother with fetching the index at all!

Similar logic can be used to optimize two dimensional accesses, say of a 30
x 30 char array, knowing that an index can't be bigger than the specified
dimensional limit.
 
C

CBFalconer

Pierre said:
.... snip ...

He was also asking about the reverse conversion,

int array_2d[10][5];
int *flat= array_2d[0];
for(i= 0; i<50; i++) {/* use flat */}

and *that* I think is technically illegal, but it's a damn shame.


Try: int *flat = &array_2d[0][0];

array_2d[0] is an array of 5 ints.
 
I

Ioannis Vranos

CBFalconer said:
Pierre Asselin wrote:
... snip ...
He was also asking about the reverse conversion,

int array_2d[10][5];
int *flat= array_2d[0];
for(i= 0; i<50; i++) {/* use flat */}

and *that* I think is technically illegal, but it's a damn shame.


Try: int *flat = &array_2d[0][0];

array_2d[0] is an array of 5 ints.



array2d[0] is an array of 5 ints and it "behaves" (I do not know the
accurate technical term)as a pointer to array_2d[0][0], that is
*array_2d[0] is array_2d[0][0].
 
I

Ioannis Vranos

Correction:


Ioannis said:
array_2d[0] is an array of 5 ints.


array2d[0] is an array of 5 ints and it "behaves" (I do not know the
accurate technical term)as a pointer to array_2d[0][0], that is

*(array_2d[0]) is array_2d[0][0].
 
C

CBFalconer

Ioannis said:
CBFalconer said:
Pierre Asselin wrote:

... snip ...
He was also asking about the reverse conversion,

int array_2d[10][5];
int *flat= array_2d[0];
for(i= 0; i<50; i++) {/* use flat */}

and *that* I think is technically illegal, but it's a damn shame.


Try: int *flat = &array_2d[0][0];

array_2d[0] is an array of 5 ints.


array2d[0] is an array of 5 ints and it "behaves" (I do not know
the accurate technical term)as a pointer to array_2d[0][0], that
is *array_2d[0] is array_2d[0][0].


No it doesn't so "behave". It behaves as an array of 5 ints.
Under many circumstances it will be passed to other routines by a
pointer to its first element. This allows accessing the array
components, but does not limit the size (leading to overrun
errors).
 
C

CBFalconer

Ioannis said:
Ioannis Vranos wrote:

Correction:
array_2d[0] is an array of 5 ints.

array2d[0] is an array of 5 ints and it "behaves" (I do not know
the accurate technical term)as a pointer to array_2d[0][0], that is

*(array_2d[0]) is array_2d[0][0].

No. Don't drop the details. *(array_2d[0]) is a pointer to an
array of 5 ints. array_2d[0][0] is an int. Just one.
 
B

Ben Bacarisse

CBFalconer said:
Ioannis said:
Ioannis Vranos wrote:

Correction:
array_2d[0] is an array of 5 ints.

array2d[0] is an array of 5 ints and it "behaves" (I do not know
the accurate technical term)as a pointer to array_2d[0][0], that is

*(array_2d[0]) is array_2d[0][0].

No. Don't drop the details. *(array_2d[0]) is a pointer to an
array of 5 ints.

The usual c.l.c style seems to be "chapter and verse, please" but that
always seems too blunt to me. Can you back this up?
array_2d[0][0] is an int. Just one.

Agreed.

Presumably in the context of 'int array_2d[10][5];'.
 
I

Ioannis Vranos

CBFalconer said:
Ioannis Vranos wrote:

Correction:
array_2d[0] is an array of 5 ints.
array2d[0] is an array of 5 ints and it "behaves" (I do not know
the accurate technical term)as a pointer to array_2d[0][0], that is
*(array_2d[0]) is array_2d[0][0].

No. Don't drop the details. *(array_2d[0]) is a pointer to an
array of 5 ints. array_2d[0][0] is an int. Just one.


Actually my correction with addition of parentheses was not needed.

Given: int array_2d[10][5];

array_2d behaves like a pointer to an array of 5 ints.
array2d_[0] behaves like a pointer to array_2d[0][0];
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top