multi dimensional arrays as one dimension array

J

jameskuyper

Barry said:
Barry said:
....
char hello[5] = "Hello";

hello is not a literal.

Agreed. Neither is char, [, 5, ], =, ;, or the white space.
Everything else in the source line is a string literal.
The string literal used to initialize it, if
it does exist in the object module (it need not), will certainly
contain the terminating '\0'.

Chapter and verse?

6.4.5-5 seems to fit. 6.4.5-6 adds confirmation.

That applies only to code like

char *hello = "Hello";

When a string literal is used as an initializer for a char array,
6.7.8p14 is the relevant clause, and according to that clause a '\0'
comes into play only if the the array has enough room for it, or if
the array size is unknown. In this case, the array has a known length
of 5, which is not sufficient room for the terminating null; therefore
a terminating null is not even required to exist.
So you think the initialization of an automatic array of char does not
involve any code to copy the initial value into the array? How does
recursion work if code is not involved?

He didn't say that code was not involved, he said that a '\0' was not
involved. One obvious possibility is that the initialization is
achieved by copying 5 chars from some location which need not contain
a null character after the fifth char.
 
K

Keith Thompson

Harald van Dijk said:
#if 0
"Hello"
#endif

Note that that string literal is a preprocessing token, not a token;
by the time it would have been converted into a token (in phase 7),
it's been eliminated (in phase 4).

But yes, a string-literal is a preprocessing-token (C99 6.4, Lexical
elements). Nicely done.

But I would argue that the string literal "Hello" doesn't contain any
strings even if it isn't eliminated during preprocessing. The object
specified by the string literal certainly does contain a string, but
that object exists only during execution; the string literal itself
exists in a C source file, where no objects of any kind yet exist.

The anonymous static array object whose existence is specified by a
string literal is not the string literal itself, though we often refer
to it as a string literal as a convenient verbal shorthand.
 
J

jacob navia

Antoninus said:
FFS... it must be, like, 6 months or something since we last went
through this completely absurdly argument that excites such passion in
the breasts of the clc pedants club.

They love to discuss this stuff over and over.
Like void main, or casting the result of malloc.

There we go. Another exciting discussion in perspective
 
K

Keith Thompson

pete said:
No.
The same reasoning implies that size_t is a standard type
and that NULL is a standard macro.
All conforming C implementations define those in <stddef.h>.

Of course, but they're *also* defined in <string.h>.

So if any function declared in <string.h> is a "string function" (I'm
still not sure that's what you meant), wouldn't it follow that any
macro defined in <string.h> is a "string macro"?
 
B

Barry Schwarz

Barry said:
Barry Schwarz wrote:
...
char hello[5] = "Hello";

hello is not a literal.

Agreed. Neither is char, [, 5, ], =, ;, or the white space.
Everything else in the source line is a string literal.

The string literal used to initialize it, if
it does exist in the object module (it need not), will certainly
contain the terminating '\0'.

Chapter and verse?

6.4.5-5 seems to fit. 6.4.5-6 adds confirmation.

That applies only to code like

char *hello = "Hello";

There is nothing in 6.4.5-5 that mentions what the string literal is
used for. It simply states that the '\0' is appended automatically
every time.
When a string literal is used as an initializer for a char array,
6.7.8p14 is the relevant clause, and according to that clause a '\0'
comes into play only if the the array has enough room for it, or if
the array size is unknown. In this case, the array has a known length

It says the '\0' is used as part of the initialization process only if
there is room. It does not say anything about the contents of the
string literal itself so you must fall back to 6.4.5-5.
of 5, which is not sufficient room for the terminating null; therefore
a terminating null is not even required to exist.

The entire literal is not required to exist. It would be perfectly
acceptable for the initialization code to consist of five independent
move instructions, each moving one character into the array.
He didn't say that code was not involved, he said that a '\0' was not
involved. One obvious possibility is that the initialization is
achieved by copying 5 chars from some location which need not contain
a null character after the fifth char.

True, but then the string literal doesn't exist either which is why my
first response above included the clause "if it exists". 6.4.5-5 does
not allow the string literal to exist without the terminating '\0'.
 
B

Barry Schwarz

Barry said:
Barry Schwarz wrote:
Richard Heathfield wrote:
(e-mail address removed) said:
(e-mail address removed) said:
<snip>

Perhaps, but in C string has a very specific meaning.
And "string literal" has a completely different one.
No, it doesn't. It is a specification, that's all. A string literal /is/
a string, /and/ it's a literal. Hence, string literal.
A string literal needs not to be a string, for example "hello\0world"
is not a string.
Right. It's several strings. (I count twelve.) I should have said "a string
literal contains at least one string".

Now show me a string literal that doesn't contain *any* strings.
char hello[5] = "Hello";
hello is not a literal.
Agreed. Neither is char, [, 5, ], =, ;, or the white space.
Everything else in the source line is a string literal.

The string literal used to initialize it, if
it does exist in the object module (it need not), will certainly
contain the terminating '\0'.
Chapter and verse?

6.4.5-5 seems to fit. 6.4.5-6 adds confirmation.
The code that initializes hello with
the literal will not copy the '\0'.
It certainly cannot "copy the '\0'," just as it cannot copy
a three-kilogram slab of luminiferous ether. As far as I can tell,
the Standard says the same thing about the existence of the former
and the latter, to wit, nothing at all.

So you think the initialization of an automatic array of char does not
involve any code to copy the initial value into the array?

No, but I think the initialization of this automatic array
(if it is in fact automatic) does not involve any code to copy
a '\0' as part of the initial value.

I agree.
The Standard citations you and Richard Heathfield offer do
appear to make your case: A character string literal always ends
with a '\0', whether it is needed or not, whether it is observable
or not. That seems to me a defect in the Standard, but since it's
survived many editorial inspections I guess I'll just have to accept
it. (Margaret Fuller: "I accept the Universe." Thomas Carlyle:
"Gad! She'd better!")

Here's another string literal whose stringiness is of no
importance:

size_t twelve = 2 * sizeof "Hello";

Since sizeof "Hello" is a compile time constant, it would seem to a
QOI issue if the literal even existed in the program. 6.4.5-5 does
not say every string literal in the source results in a string literal
in the object. It only says that if it does it must have the
terminating '\0'.
I can think of no C program that could test whether the six
characters generated by this string literal do or do not actually
exist, and it seems a shame that the Standard promulgates an
untestable requirement. Maybe "as if" saves the day.

There seems to be agreement in this group that the optimizer can do
away with unreferenced variables and unreachable code. I see no
reason why the same should not apply to unused constants.
 
B

Barry Schwarz

Of course, but they're *also* defined in <string.h>.

So if any function declared in <string.h> is a "string function" (I'm
still not sure that's what you meant), wouldn't it follow that any
macro defined in <string.h> is a "string macro"?

Could one then say that size_t is a string type and a stddef type and
a stdlib type ...?
 
A

Antoninus Twink

You mistake me. Jacob is not a troll, not in my opinion at
any rate. He argues in an offensive manner for some positions
I strongly disagree with, but he impresses me as sincere.

You can be a patronizing bastard sometimes, Sossman. Jacob is one of the
most knowledgable and helpful posters to clc and he shows remarkable
patience in the face of sustained bullying from you and your "friends".
I don't think he needs your condescension.
The troll he fed, however, is a waste of food and oxygen.

Do you really think so Eric? Truth is, there have been MANY threads over
the last few months where the ONLY constructive technical reply was from
me - a lone voice in the barrage of aggressive "not C, not portable,
can't discuss it, go **** yourself, don't care if you live or die"
responses. I'd say that's a useful contribution to the group.

Of course, I also make a point of trying to stick a pin in the
ballooning egos of some of the regulars here - that's never going to
make a person popular.
 
T

Tim Rentsch

The subject might be misleading.
Regardless, is this code valid:
#include <stdio.h>
void f(double *p, size_t size) { while(size--) printf("%f\n", *p++); }
int main(void) {
double array[2][1] = { { 3.14 }, { 42.6 } };
f((double *)array, sizeof array / sizeof **array);
return 0;
}
Assuming casting double [2][1] to double * is implementation defined
or undefined behavior, replace the cast with (void *).
Since arrays are not allowed to have padding bytes in between of
elements, it seems valid to me.

Despite what some other people have been saying,
this is valid. If foo is an array, doing (some_type *)foo
gives access to all of foo. Since 'array' is made up
(ultimately) of double's, using '(double*)array' will
work just fine.

Well, now I'm at loss again. I think the only way to settle this is to
provide quotes from the standard that agree (or disagree) with you.

I don't really expect to convince anyone, but for those who are
interested here is a citation plus some reasoning.

As far as I can tell, the standard never defines the term array,
but it does define 'array type', in 6.2.5 p 20:

An array type describes a contiguously allocated nonempty set of
objects with a particular member object type, called the element
type.

(In the text, 'array type' and 'element type' are italicized.)

In the circumstance given above (double array[2][1]), there
certainly is a contiguously allocated nonempty set of objects,
all of type double.

Now, what is accessible. The principle is that taking the
address of an element in an array allows access to other elements
in the array. We see this all the time with ordinary arrays;
here are some other cases:

int m[3][5];
volatile int (*vm)[5] = (volatile int (*)[5]) &m[0];

Access through vm is the same as through m, except that access
through vm treats elements as volatile.

_Complex double z[10];
double (*ri_z)[2] = (double (*)[2]) &z[0];

Access through z gives one-dimensional access to complex values;
access through ri_z gives component access, with ri_z[0] being
the real part of z, and with ri_z[1] being the imaginary
part of z.

Pretty obviously, we expect the above cases to work. And there
is nothing special about using 0 in &m[0] or &z[0]; using 1
instead would work fine, if the first index to vm or ri_z were
adjusted accordingly. Casting the address of an array element
preserves access to the entire array; since that is true in
these cases, it should also be true for another cast, namely:

double array[2][1] = { ... };
double *one_d = (double*) array; /* same as &array[0] */

Because what's being converted is the address of an element
of 'array', all of 'array' is accessible through the converted
pointer, just as it would be in the other cases above.

Whether this is all convincing depends crucially on what is
meant in 6.5.6 p 8 by the phrase "points to an element of an
array object". Unfortunately this key phrase is not explained
clearly. What is the most consistent reading of all that the
standard says? My view is expressed above, along with some
of the reasoning behind it.
 
T

Tim Rentsch

James Kuyper said:
Tim Rentsch wrote:
...

That's where your argument breaks down. A double* is governed by rules
about the limits of pointer addition that char* is specifically exempted
from, and which are meaningless for void*. I've described the problem in
more detail in another branch of this discussion, so I won't repeat the
description here.

Presumably you're talking about 6.3.2.3p7, more specifically the
last two sentences:

When a pointer to an object is converted to a pointer to a
character type, the results points to the lowest addressed
byte of the object. Successive increments of the result, up
to the size of the object, yield pointers to the remaining
bytes of the object.

These sentences don't say anything about accessing other elements
of an array; they apply only to the single object pointed at.
(Read literally they don't say anything about indexing or pointer
arithmetic either, only "successive increments", but let's ignore
that.) The access afforded here is only within the object pointed
at, not any surrounding array that the object might be in. In fact,
if we judge just by the statements here, then the code

int x[10];
unsigned char c, *p;
*p = (unsigned char*) &x[2];
c = p[ 2 * sizeof x[2] ];

yields undefined behavior, because the index is bigger than the
size of the object pointed at (namely, x[2]).

So unless you have another reference to cite, the claim that char *
is exempted from limits on pointer addition is unsupported by the
standard.
 
T

Tim Rentsch

James Kuyper said:
Tim said:
James Kuyper said:
James Tursa wrote:
...
OK, that's fine for objects, but that doesn't answer my question. What
is it about 2-dimensional (or multi-dimensional) arrays of double that
does not allow them to be stepped through with a double* ?
Ultimately, nothing more or less than the fact that the standard says
that the behavior is undefined. Because the behavior is undefined,
compilers are allowed to generate code that might fail if such stepping
is attempted (though this is rather unlikely). More importantly,
compilers are allowed to generate code that assumes that such stepping
will not be attempted, and therefore fails catastrophically if it
actually is attempted - the most plausible mode of failure is a failure
to check for aliasing.

Specific details:

Given

double array[2][1];
double *p = (double*)array;

If there is code which sets array[1] to one value, and p[j] to
another value, the compiler not required to consider the possibility
that p[j] and array[1] might point at the same location in memory.
It's allowed to keep either value in a register, or to keep the two
values in different registers. It's not required to make the next
reference to array[1] give the same value as the next reference to p[j].

This is because the behavior would be undefined if 'i' and 'j' had
values that might ordinarily cause you the expect array[1] and p[j]
to refer to the same location. Note: this convoluted wording is
necessary, because if 'i' and 'j' have such values, then at least one of
the two expressions has undefined behavior, rendering it meaningless to
talk about which location that expression actually refers to.


You're starting with the conclusion, and then "proving" the
conclusion. This conclusion isn't consistent with other
behavior and language in the standard.


I was not trying to prove that the behavior was undefined. I was trying
to explain how it is that the fact that the behavior is undefined can
make it dangerous to rely upon such code.

I've presented my argument that the behavior IS undefined elsewhere,
most recently in the response I just posted to Ben Bacarisse.

...
Irrelevant, because that's talking about whether a memory access
can have undefined behavior because of an invalid representation.
It's just as illegal to access outside of an array using unsigned
char as it is using double. The only question is, what memory
may be accessed. Since 'array' is what was converted, any memory in
array may be accessed.

There are multiple arrays involved, and the question is - which array is
the one which 6.5.6p8 is referring to? A careful examination of 6.5.6p8
reveals that what is says constitutes utter nonsense unless the element
type of the relevant array is the same as the type pointed at by the
pointer. Example:

int matrix[3][5];
int *pi = matrix[1];

For purposes of integer additions to "pi", if the array referred to by
6.5.6p8 were "matrix" rather than matrix[1], then because pi points at
the second element of matrix, matrix[1], pi+1 would have to point at the
third element, matrix[2]. If "matrix" were the relevant array, then the
largest amount which could be added to matrix[1] would be 2, not 5,
because the length of matrix is only 3, while the length of matrix[1] is 5.

This is a wholly indefensible interpretation of 6.5.6p8. The only array
that it could possibly be referring, when applied to "pi", is matrix[1].


A plausible analysis, but not on point, since the example code
above doesn't cast array[1], it casts array, which allows access
to the whole object.

People have claimed that there is a one-dimensional array of 15 ints
which should be used when applying 6.5.6p8, but I see no such array
declared anywhere in the above code.

This whole argument seems predicated on the assumption that arrays
exist only where there are array declarations. Clearly this
assumption is false:

int *p = malloc( 15 * sizeof *p );

No array declarations, but indexing of p is allowed (assuming of
course that p != NULL).

Or, how about this:

int flat[15];
int (*not_flat)[3] = (int (*)[3])flat;

Since the only array object declared here is flat, by your
reasoning an access like not_flat[2][4] should be legal.
 
T

Tim Rentsch

Harald van =?UTF-8?b?RMSzaw==?= said:
[restoring snipped portion}
int main(void) {
char array[2][1] = { { 'a' }, { 'b' } };
f((char *)array, sizeof array / sizeof **array);
return 0;

}
OK, that's fine for objects, but that doesn't answer my question.
What is it about 2-dimensional (or multi-dimensional) arrays of
double that does not allow them to be stepped through with a double*
?

The fact that double[2][3] doesn't have elements such as x[0][5]. There
must be a valid double, 5*sizeof(double) bytes into x. However, x[0][5]
doesn't mean just that. x[0][5] (or ((double*)x)[5]) means you're
looking 5*sizeof(double) bytes into x[0]. x[0] doesn't have that many
elements.

That doesn't matter since array isn't being accessed as a
two-dimensional array. Converting array (not array[0], but array) gives
a pointer that has access to all the same memory as array.

With the exception of character types, does the standard describe the
conversion of an array to anything other than its initial element?
Strictly speaking, I can't even find where the standard describes the
result of converting double(*)[3] to double* at all, but the only way to
perform that conversion indirectly is by taking the address of the first
element of the first sub-array, and I accept that a direct conversion
should mean the same thing. If you can point out where more permissions
are given, please do so.

Conversion of one pointer type to another is always allowed,
subject to the condition that the pointer in question is
suitably aligned for the new type. The alignment of the
array pointer type double(*)[3] is guaranteed to be sufficient
for conversion to double*, because the array pointer type
points at something that has exactly three doubles in it,
and specifically has one right at the beginning.

Does it help if this conversion is written

f( (double*) &array[0], sizeof array / sizeof **array )

instead?

The principle is, taking the address of an element of
an array preserves access to the entire array that the
element is in. That is, we expect for

T xyz[ BLAH ];

g( & xyz[ k ] );

that g will be able to access all of xyz, even though
what was passed is just a pointer to the k'th element.

That make more sense now?
 
T

Tim Rentsch

James Kuyper said:
Tim Rentsch wrote:
...

The standard says nothing about that. It says remarkably little about
what (sometype*)X does in general. Most of what it does say is that,
under some circumstances (none of which are relevant to this case),
conversion back to the original type returns a pointer that compares
equal to the original. Of the few cases where it says anything more than
that about what the result of (sometype*)X is, none apply here.

Yes, the standard doesn't explicitly say exactly that, but it's
the most consistent reading based on what is said. There's
plenty of other examples of similar cases, such as:

1. If we have an array x of type T, then (T*)&x == &x[0].

2. If we have a type struct X with a (non-bitfield) member m,
and there is a variable s of type struct X, then
(struct X*) ((char*)&s.m - offsetof(struct X,m)) == &s.

3. If p is a pointer to an array element, then (unsigned char*)p
can be used, as though it pointed into an array of unsigned
char, to access the entire original array.

The standard doesn't say any of these things, yet most people
accept that they are correct, because in each case it's the
most consistent reading based on what the standard does say.

Of course, some people may prefer a strict constructionist view
like the one you express above, but they should be prepared to
reject cases 1, 2, and 3 (among others) as well; you can't have
it both ways. Personally, I think the "most consistent reading"
model is more likely to yield results that agree with how other
people (both on and off the committe) read it.
 
J

James Kuyper

Tim Rentsch wrote:
....
Presumably you're talking about 6.3.2.3p7, more specifically the
last two sentences:

When a pointer to an object is converted to a pointer to a
character type, the results points to the lowest addressed
byte of the object. Successive increments of the result, up
to the size of the object, yield pointers to the remaining
bytes of the object.

These sentences don't say anything about accessing other elements
of an array; they apply only to the single object pointed at.

When you define

int matrix[3][5];

matrix[1][2] is an object. So is matrix[2], and so it matrix itself. The
standard specifically acknowledges that there can be multiple different
overlapping objects at any given memory location. 6.3.2.3p7 applies to
all three objects, not just the smallest one. If you apply 6.3.2.3p7 to
"matrix" as a whole, it prohibits bounds checking of unsigned char*
pointers that point anywhere inside of "matrix"; only when pointer
addition pushes such a pointer across the boundaries of "matrix" itself
is bounds-checking allowed.
 
J

James Kuyper

Tim Rentsch wrote:
....
A plausible analysis, but not on point, since the example code
above doesn't cast array[1], it casts array, which allows access
to the whole object.

Citation, please - where does the standard say that such a conversion
allows access to the whole object? Where, in fact, does the standard say
anything at all about what you can do with the converted pointer value,
other than convert it back to it's original type?

My argument depends only upon the type of the pointer, not on the
sequence of conversions that produced that pointer value.
This whole argument seems predicated on the assumption that arrays
exist only where there are array declarations.

Spot on!
... Clearly this
assumption is false:

int *p = malloc( 15 * sizeof *p );

No array declarations, but indexing of p is allowed (assuming of
course that p != NULL).

I believe that this is covered by the standard's promises that "The
pointer returned if the allocation succeeds is suitably aligned so that
it may be assigned to a pointer to any type of object and then used to
access such an object or an array of such objects in the space allocated
(until the space is explicitly deallocated)."

I do not agree that this provides general permission to treat any
arbitrary sequence of 'int's as an array just because they happen to
occupy memory in the same way that they would if they were declared as
an array of int.
Or, how about this:

int flat[15];
int (*not_flat)[3] = (int (*)[3])flat;

Since the only array object declared here is flat, by your
reasoning an access like not_flat[2][4] should be legal.

The result of the conversion you performed there is not specified by the
standard; the only thing that the standard guarantees about that
converted pointer value is that, if you convert it back to the original
type, it will compare equal to the original pointer.
 
H

Harald van Dijk

Conversion of one pointer type to another is always allowed, subject to
the condition that the pointer in question is suitably aligned for the
new type.

Correct. And the result is specified as a pointer that you can convert
back to the original type to get something comparing equal to the original
pointer, but nothing more than that. In the special case of a character
type, the standard points out that the result points to the first byte of
the same object, and allows access to all bytes of the same object. If it
was necessary to explicitly specify this for character types, why does it
implicitly apply to other types as well?
 
T

Tim Rentsch

Harald van =?UTF-8?b?RMSzaw==?= said:
Correct. And the result is specified as a pointer that you can convert
back to the original type to get something comparing equal to the original
pointer, but nothing more than that. In the special case of a character
type, the standard points out that the result points to the first byte of
the same object, and allows access to all bytes of the same object. If it
was necessary to explicitly specify this for character types, why does it
implicitly apply to other types as well?

The two cases aren't the same. In the case of, say, an int
matrix, such as

int m[3][5];

the storage for m is guaranteed to hold contiguous int objects,
with the same representation as an array of int. The "arrayness"
is already there.

Converting to (char*) is different because the object being pointed
at may be just a scalar; even non-array objects can have a
character array imposed on top of them, by converting their
address to (char*) (and similarly (unsigned char*)).

It's because of imposing an array representation onto a non-array
object that character pointers are singled out in defining their
conversion semantics.
 
T

Tim Rentsch

James Kuyper said:
Tim Rentsch wrote:
...
Presumably you're talking about 6.3.2.3p7, more specifically the
last two sentences:

When a pointer to an object is converted to a pointer to a
character type, the results points to the lowest addressed
byte of the object. Successive increments of the result, up
to the size of the object, yield pointers to the remaining
bytes of the object.

These sentences don't say anything about accessing other elements
of an array; they apply only to the single object pointed at.

When you define

int matrix[3][5];

matrix[1][2] is an object. So is matrix[2], and so it matrix itself. The
standard specifically acknowledges that there can be multiple different
overlapping objects at any given memory location. 6.3.2.3p7 applies to
all three objects, not just the smallest one. If you apply 6.3.2.3p7 to
"matrix" as a whole, it prohibits bounds checking of unsigned char*
pointers that point anywhere inside of "matrix"; only when pointer
addition pushes such a pointer across the boundaries of "matrix" itself
is bounds-checking allowed.

It seems to me you're reinforcing my point. If we have

char *p = &matrix[1][2];

then p points to the int object matrix[1][2]. It does not point
to matrix, or matrix[1], or matrix[2]. 6.3.2.3p7 applies only to
the object pointed to, not other objects that happen to surround
that object; the phrasing "successive increments" makes that
clear.

Pointers to character types have exactly the same extent
restrictions that other pointer types have. 6.3.2.3p7 is there
only to explain what happens when character indexing is performed
inside those extent restrictions. I don't see any evidence to
support the claim that character-type pointers are exempt from
the same extent restrictions that other pointer types have.
 
T

Tim Rentsch

James Kuyper said:
Tim Rentsch wrote:
...
A plausible analysis, but not on point, since the example code
above doesn't cast array[1], it casts array, which allows access
to the whole object.

Citation, please - where does the standard say that such a conversion
allows access to the whole object? Where, in fact, does the standard say
anything at all about what you can do with the converted pointer value,
other than convert it back to it's original type?

Which view is more reasonable:

A. Pointer conversion yields a pointer to the same object as
the original (assuming no alignment problems); or

B. Pointer conversion follows a strict constructionist view -
the only thing you can do with a converted pointer is
convert it back to the original type and compare it against
the unconverted original (assuming non-char types, etc)?

Of course, no one really believes (B); if they did, then they
should insist that a code sequence like

int i = 0;
const int *p = &i;
return *p;

produces undefined behavior.

Are you advocating a strict constructionist interpretation along
the lines of B? If not, then what interpretation are you
advocating?

My argument depends only upon the type of the pointer, not on the
sequence of conversions that produced that pointer value.

What I think you mean is that your rule about what's allowed
depends only on the type of the pointer, although whether that's
the type before conversion or after conversion I'm not sure.
Surely your argument depends at least in part on what is said in
the standard. Can you better articulate what your position is?

Spot on!


I believe that this is covered by the standard's promises that "The
pointer returned if the allocation succeeds is suitably aligned so that
it may be assigned to a pointer to any type of object and then used to
access such an object or an array of such objects in the space allocated
(until the space is explicitly deallocated)."

So you're changing your previous position that arrays exist only
where there are array declarations?

I do not agree that this provides general permission to treat any
arbitrary sequence of 'int's as an array just because they happen to
occupy memory in the same way that they would if they were declared as
an array of int.

You're doing a better job of saying what you think the rules
aren't than saying what you think the rules are.
Or, how about this:

int flat[15];
int (*not_flat)[3] = (int (*)[3])flat;

Since the only array object declared here is flat, by your
reasoning an access like not_flat[2][4] should be legal.

The result of the conversion you performed there is not specified by the
standard; the only thing that the standard guarantees about that
converted pointer value is that, if you convert it back to the original
type, it will compare equal to the original pointer.

What is certainly true is that the standard guarantees AT LEAST
that the value of not_flat can be converted back to int* and
compare equal to flat (with the usual disclaimer about
alignment). The question is, does it guarantee anything else,
and if so what? If you want to adopt a strict constructionist
interpretation, along with the consequences of that, well, to
each his own. I think most people believe that interpretation
is not really sensible as regards pointer conversion.

What I think most people believe, and what the standard says
(somewhat poorly) and also intends, is this:

1. If (e) is an expression of array type, then &(e) is a
pointer that allows access to all elements in the array object
designated by (e); this array object is limited by both the
number of elements in the array type (if it's a complete type)
and the extent of the outermost containing object, and of course
i must be an allowed index for that range.

2. Assigning a pointer value of the same type, or computing a new
pointer value based on pointer arithmetic, produces a new pointer
value with the same byte range as the original pointer.

3. Converting a pointer value produces a new pointer value that
points to the same object as, and has the same byte range as, the
orginal pointer (with the usual alignment disclaimer).

4. A pointer derived from the result of doing a malloc(N) is the
same as converting from a pointer value *(char(*)[N])malloc(N)
(here of course malloc guarantees no alignment problems).

I believe these are correct because they are most consistent with
everything the standard says about what a pointer is, what arrays
are, what conversion is, how pointer arithmetic works, what may
be expected for multi-dimensional array objects, and the rules
for effective type and object access, among others. If have a
different opinion, that's fine, but please suggest a set of rules
covering the cases included in 1-4 that offer a more consistent
viewpoint than the one given here.
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top