Newbie

M

maverick

Hi,
Here is a piece of code

#include<stdio.h>
main()
{
int arr[3]={1,0,2};
int **dp;
int (*pa)[3];
int i = 0;
pa = &arr;
dp = arr;
printf("%u \t %u \n",arr,&arr); ---> stmt A
printf("%u \t %u \n",pa,*pa); ----> stmt B
printf("%u \t %u \n",dp,*dp); -----> stmt C

for(;i<3;i++)
{
printf("arr %d\t pa %d\t dp %d\n",*(arr+i),*(*pa+i),*(dp+i)); -->stmt D
}
}

here is the output

2062498376 2062498376
2062498376 2062498376
2062498376 1
arr 1 pa 1 dp 1
arr 0 pa 0 dp 0
arr 2 pa 2 dp 2

Whats bugging me here is
a) why both the values in stmt A are equal.
b) why both the values in stmt B are equal
c) why the values in stmt C are not equal
d) why shouldn't I use *(pa+i) instead of *(*p+i) in stmt D
e) how come *(dp+i) works fine in stmt D

any help would be appreciated.
maverick
 
C

Chris Torek

Here is a piece of code

.... with undefined behavior; why this matters (and when it does not)
in a moment:
#include<stdio.h>
main()
{
int arr[3]={1,0,2};
int **dp;

[this is a typo -- you mean "int *dp", otherwise you must get
compiler diagnostics from any conforming C compiler]
int (*pa)[3];
int i = 0;
pa = &arr;
dp = arr;
printf("%u \t %u \n",arr,&arr); /* stmt A */
printf("%u \t %u \n",pa,*pa); /* stmt B */
printf("%u \t %u \n",dp,*dp); /* stmt C */

I am going to address just these for the moment.

[output so far]
2062498376 2062498376
2062498376 2062498376
2062498376 1
Whats bugging me here is
a) why both the values in stmt A are equal.
b) why both the values in stmt B are equal
c) why the values in stmt C are not equal

Here is the first (and major) problem: the "values" in statements
A and B are *not* equal. The output *numbers* are equal, but this
is the way your implementation happens to handle the undefined
behavior. The values in statement C are indeed not equal, and
the output of "1" is as expected (disregarding the earlier undefined
behavior anyway).

C has a very odd "feature" in dealing with arrays. This feature
is the source of a great deal of confusion. To understand it, you
must start by understanding a fundamental division that C shares
with many other languages, so you might well already know it, but
it is good to get some verbal handles on it.

C separates most of the "C world" into "objects" and "values".
Objects are simply regions of storage that hold values. (Ordinary
variables are objects.) Values are more or less what you expect:
numbers like 42 and 3.14159, letters like 'a', and so on. All
values have a type: 42 has type "int", 3.14159 has type "double",
and 'a' has type "int" (perhaps surprisingly -- it seems like it
should be "char" but it is actually "int"; note that C++ differs
here). Values also include (unless you are Joe Wright :) ) "pointer
values", which are just values that have some particular pointer
type.

Because C does different things with differently-typed values,
whenever we want to describe a value, we must also attach its type.
For instance, 3 / 4 is 0, but 3.0 / 4.0 is 0.75 -- so if you want
to talk about the int value 3 and the int value 4, I recommend
writing them as pairs for the ultimate in explicitness: <int, 3>
and <int, 4>. That way you can write <double, 3> to mean 3.0, and
you can also write <char, 'a'> to mean the value you get by converting
the "int" value 'a' to "char" (as you would have to do to store it
in a "char" object). Often you can "cheat" and leave out the type
part, inferring it from context: 3 must be an int, while 3.0 must
be a double. But if you write it down -- <double, 3.0> -- it
is clearer; and it lets you talk about <float, 3.0> without using
the funky F suffix (3.0F). It also lets you talk about pointer
values, which need types too.

Like many other languages, C will automatically let you write an
object where a value is required:

int i, j;
...
i = j;

Here i and j both name objects of type int, yet somehow this
assignment statement finds the *value* of j, and copies that
into the *object* i. If you write:

i = i + j;

the statement finds the *values* of i and j, computes the sum of
those two values, and stores the result into the *object* i. How
does the compiler manage this trick? (It is a neat trick, if you
think about it for a bit.)

The answer lies in what I call "object versus value context".
The left hand side of an assignment operator like "=" has "object
context", while the right hand side has "value context". So in
"i = j", the compiler knows to use i as an object -- to store a
new value -- and yet to pull a value out of j. In "i + j", both
i and j are operands to the "+" operator, which takes two values,
so the compiler knows that you want it to find the values stored
in those objects.

With all this in mind, we can finally get to the peculiar thing C
does with arrays. C has a special rule for array objects. This
rule is crucial to understanding arrays and pointers in C. It is
so important, so central, that I call it not "a rule" but "The
Rule", with capital letters. There is no fundamental reason for
The Rule about arrays and pointers in C; it is just something Dennis
Ritchie came up with, that you have to memorize. It goes like
this:

In a value context, an object of type "array N of T" becomes
a value of type "pointer to T", pointing to the first element
of that array, i.e., the one with subscript 0.

Armed with The Rule, let us now take a look at just this part:

int arr[3] = { 1, 0, 2 };
int *dp;
dp = arr;

Now, the ordinary assignment in the last line has object context
on the left and value context on the right -- and "arr" is the
name of an object with type "array 3 of int". So, we must
apply The Rule: "an object of type array N of T" (yep, N=3 and
T=int) "becomes a value of type pointer to T" (pointer to int)
"pointing to the first element of that array" (&arr[0]). So
dp is an object, and its value becomes the pair:

<pointer to int, &arr[0]>

But what about "pa", in:

int (*pa)[3];
pa = &arr;

? Here &arr applies the unary "&" operator to the array object.
The unary "&" operator does *not* take a value; it takes an object.
Here The Rule does *not* apply -- unary "&" has "object context"
instead of "value context". Given a valid object O of some type
T, &O produces a value of type "pointer to T", pointing to that
object. Since "arr" is an object of type "array 3 of int", &arr
yields the address of arr, as a value of type "pointer to array 3
of int". And, since "pa" has type "pointer to array 3 of int",
it can certainly hold that value.

So, now consider:

printf("%u \t %u \n",arr,&arr); /* stmt A -- undefined behavior */

Here "arr" and "&arr" are both passed by value to printf(). This
puts each one in value context. Since "arr" names an array object,
The Rule applies to the middle part, and the value delivered to
printf() is:

<pointer to int, &arr[0]>

On the other hand, &arr produces a value, and no special action
is required; The Rule does not kick in. Here the value delivered
to printf() is:

<pointer to array 3 of int, &arr>

But the format argument to printf has two "%u" conversions, and
when you give printf a "%u", you tell it to pluck an "unsigned
int" value out of the variable parameters. In other words,
printf() is grabbing some <int, ___> (fill in the blank) value
from somewhere. Your actual output was:

2062498376

but what the heck is that? If you were using an old Motorola 68000
based system, it might be whatever was lying around in register
D0, even though the pointer value you gave printf() was sent down
in register A1.

As it happens, my crystal ball tells me you were probably using an
Intel x86 CPU in 32-bit mode -- and nothing like gcc's -mregparm
trick -- so "2062498376" was whatever was at (%esp+8). That happens
to be the numeric value corresponding to a typeless pointer
corresponding in turn to &arr[0]. But all of this is quite
machine-specific: your machine happens to discard the type information
from pointer values at runtime, *and* has pointer values the same
size as ordinary int values, *and* passes them in the same location
to printf(). (All three of these are actually quite common today,
but were not once, and are soon not to be again. In particular,
32-bit-int 64-bit-pointer is no longer terribly rare.)

The second "%u" conversion also printed 2062498376, but again, it
was just by luck (whether good or bad is a matter of interpretation),
or perhaps by "knowing too much" about your machine. It is not
particularly surprising, but if your machine had different kinds
of pointer values in hardware, and used them for C code; or if it
passed pointers in A registers and int values in D registers; or
if "int" and (some) pointers were different sizes -- if any of
these had been true, you would perhaps not have gotten two identical
numeric values.

Lastly, before I move on to the next part, consider the two
pointer values:

<pointer to int, &arr[0]>
<pointer to array 3 of int, &arr>

C has a funny little rule about every object -- including array
objects -- being made up of bytes, so we can convert either of
these two pointer values to a "byte pointer", which C spells
"char *" or "unsigned char *" or -- something added in 1989 purely
to allow us to avoid casts -- "void *". Suppose we convert both
of those two pointer values to "void *":

void *vp1, *vp2;
vp1 = arr; /* by The Rule, "arr" becomes <int *, &arr[0]> */
vp2 = &arr; /* "&arr" is just <int (*)[3], &arr> */

Now we have, in vp1 and vp2, two pointer values of type "void *".
Each of these points to the first byte in the object to which
the original pointers (&arr[0] and &arr) pointed. Those two
"first bytes" are necessarily the *same* byte, so if we were
to use a C-standard-defined printf():

printf("vp1=%p\nvp2=%p\n", vp1, vp2);

we might expect the two values printed to be equal. (But on
the other hand, %p's output format is up to the implementation,
and if the implementation keeps extra information -- such as
for debugging -- about the way those "void *" pointers were
derived, it might print out the debug info and show us values
that are slightly different, e.g.:

vp1=0x7aef3a48 (from <int *>0x7aef3a48)
vp2=0x7aef3a48 (from <int (*)[3]>0x7aef3a48)

Systems that track this much detail are rare, presumably because
no one ever needs to debug C code. :) )

We can fix "stmt C" to have well-defined output by making sure
that the printf formats and arguments have matching types:

printf("dp=%p *dp=%d\n", (void *)dp, *dp);

Here the cast to "void *" converts the value in dp to the type we
told printf to expect when we wrote "%p", and the type of *dp --
assuming "dp" is "int *dp" -- is in fact int, which is what %d says
for printf to print.

Now, still with:

int arr[3] = { 1, 0, 2 };
int *dp = arr;
int (*pa)[3] = &arr;

and with "int i" initially zero:
for(;i<3;i++)
printf("arr %d\t pa %d\t dp %d\n",*(arr+i),*(*pa+i),*(dp+i)); /*stmt D*/
[produces]

arr 1 pa 1 dp 1
arr 0 pa 0 dp 0
arr 2 pa 2 dp 2

d) why shouldn't I use *(pa+i) instead of *(*pa+i) in stmt D
e) how come *(dp+i) works fine in stmt D

Now we come to the definitions of the "+" and unary "*" operators
when applied to pointers. (Unary * can ONLY be applied to pointers,
but + works on other things too.)

First, it is worth mentioning that C's subscript operation a
is defined in terms of unary "*" and pointer arithmetic, so that
a "means" *((a) + (b)). We can work this in reverse, too,
rewriting *(arr + i) as arr, and so on. It is also worth
noting that unary * binds more tightly than +, so that *pa+i
"means" (*pa) + i.

Second, the definition of *(ptr) is "follow the pointer value,
producing the object to which it points". The definition of
"ptr_value + int_value" is "move the pointer forward by the given
number of objects (or backward is the number is negative)". For
instance, p+2 is "move forward by 2 of whatever it is p points to".
The "whatever it is" part is determined by the type of the pointer
-- if the pointer has type "pointer to T", we will move forward
two "T"s. As usual in C, the type of the value is critical:
without it we cannot find the answer.

Last, as always, remember The Rule.

Let us take *(arr + i) first. Here we ask the "+" operator to add
the values of "arr" and "i". What is the value of arr? Well,
"arr" names an array object. Voila! An array object in a value
context; The Rule applies. So we apply it, and find that the
"value" of "arr" is &arr[0], which has type "pointer to int". Now
we add i -- 0, 1, or 2 -- so we move forward 0, 1, or 2 "int"s in
the array. Then we apply the unary *, following this pointer value
to its object, which is an object of type "int". That object
happens to be arr -- which is of course what *(arr + i) means,
while *(arr + i) is what arr means. This is how The Rule does
its magic.

Now consider *((*pa) + i). This has TWO occurrences of unary *.
The one inside the parentheses is *pa, which means "find the value
of pa, then follow it to its object". The value is the value we
stored in the object named "pa" earlier:

<pointer to array 3 of int, &arr>

When we follow this to its object, we get an object of type "array
3 of int" -- this being what is left after removing the initial
"pointer to" part. Note that we have an object, and it has an
array type. Now we want to compute that plus the value in i --
and what do you know, it is time once again for The Rule. We have
an array object and we want its value. The object is "arr" itself,
and its "value", by The Rule, is a pointer value of type "pointer
to int", pointing to the first element of "arr". The rest of
the sequence happens just as before.

Note that, as always, we can rewrite *(a + b) as a. In this
case we can change *((*pa) + i) into (*pa). The "*" on "*pa"
*must* be enclosed in parentheses this time, though, because *pa
would "mean" *(pa), which is something quite different. (If we
wrote that, it would be the same as *(*(pa + i)) -- adding the
value stored in i to the value stored in pa, using the type that
"pa" points to, and hence moving forward by 0, 1, or 2 "array 3 of
int"s, because pa has type "pointer to array 3 of int".)

The last expression, *(dp + i), works in the same fashion. This
time there are no array objects at all: dp is an object of type
"pointer to int", to which we add 0, 1, or 2 and move forward by
that many "int"s; then we follow the resulting pointer and get an
"int". And of course, this can be rewritten as dp, so the
last printf() line is the same as:

printf("arr %d\tpa %d\tdp %d\n", arr, (*pa), dp);

One final note: if a "means" *((a) + (b)), consider a[0].
Here we have *((a) + 0). Adding 0 to a pointer means "do not
move the pointer at all", so we can get rid of the +0 part
and just write *(a). This in turn means that *(p) and (p)[0]
alwayws mean exactly the same thing (I used the parentheses
in case there is an expression involved). So (*pa) is just
pa[0], and the above printf() can also be written:

printf("arr %d\tpa %d\tdp %d\n", arr, pa[0], dp);

This last line may give readers an "aha!" moment: a "pointer
to array N of T", like pa is a "pointer to array 3 of int",
has to be subscripted *twice*. We write pa[0] to access
the i'th element of the 0'th -- and in this case only -- array
to which "pa" points. What if we wrote pa[j]? Would this
be the i'th element of the j'th array? How many arrays does
"pa" access in this way? How does this compare to the number
of "int"s we can access with dp?
 
M

maverick

Hi,
#include<stdio.h>
main()
{
int arr[3]={1,0,2};
int **dp;
[this is a typo -- you mean "int *dp", otherwise you must get
compiler diagnostics from any conforming C compiler]

This is not a typo. I did mean "int **dp". I had got compiler diagnostics
(%CC-W-PTRMISMATCH)

from somewhere. Your actual output was:



but what the heck is that? If you were using an old Motorola 68000
based system, it might be whatever was lying around in register
D0, even though the pointer value you gave printf() was sent down
in register A1.

As it happens, my crystal ball tells me you were probably using an
Intel x86 CPU in 32-bit mode -- and nothing like gcc's -mregparm
trick -- so "2062498376" was whatever was at (%esp+8). That happens

I was using a VMS Alpha m/c.

Maverick.

maverick said:
Hi,
Here is a piece of code

#include<stdio.h>
main()
{
int arr[3]={1,0,2};
int **dp;
int (*pa)[3];
int i = 0;
pa = &arr;
dp = arr;
printf("%u \t %u \n",arr,&arr); ---> stmt A
printf("%u \t %u \n",pa,*pa); ----> stmt B
printf("%u \t %u \n",dp,*dp); -----> stmt C

for(;i<3;i++)
{
printf("arr %d\t pa %d\t dp %d\n",*(arr+i),*(*pa+i),*(dp+i)); -->stmt D
}
}

here is the output

2062498376 2062498376
2062498376 2062498376
2062498376 1
arr 1 pa 1 dp 1
arr 0 pa 0 dp 0
arr 2 pa 2 dp 2

Whats bugging me here is
a) why both the values in stmt A are equal.
b) why both the values in stmt B are equal
c) why the values in stmt C are not equal
d) why shouldn't I use *(pa+i) instead of *(*p+i) in stmt D
e) how come *(dp+i) works fine in stmt D

any help would be appreciated.
maverick
 
A

Anupam

This last line may give readers an "aha!" moment: a "pointer
to array N of T", like pa is a "pointer to array 3 of int",
has to be subscripted *twice*. We write pa[0] to access
the i'th element of the 0'th -- and in this case only -- array
to which "pa" points. What if we wrote pa[j]? Would this
be the i'th element of the j'th array? How many arrays does
"pa" access in this way? How does this compare to the number
of "int"s we can access with dp?



It would move forward pa by j "array 3 of ints" then on applying *
we'd get an "array 3 of int" which by the rule would become a pointer
to an int to the first element in this array and move this forward by
i ints. Of course this is a BIG FAT nasal demon if used for the above
code with j > 0. pa would have access to only one such array element
of type "array 3 of int".
while dp has access to three integers.

I hope thats right. Any critique would be welcomed.

Also I must admit that the above was just a side-track. Basically I
wanted to congratulate you on an excellent excellent post. That was
real great. Enjoyed it tremendously.
Thanks and regards ,
Anupam
 
C

Chris Torek

[Given "int (*pa)[3]", with "pa" set to a suitable value...]

This last line may give readers an "aha!" moment: a "pointer
to array N of T", like pa is a "pointer to array 3 of int",
has to be subscripted *twice*. We write pa[0] to access
the i'th element of the 0'th -- and in this case only -- array
to which "pa" points. What if we wrote pa[j]? Would this
be the i'th element of the j'th array? ...[/QUOTE][/QUOTE]

It would move forward pa by j "array 3 of ints" then on applying *
we'd get an "array 3 of int" which by the rule would become a pointer
to an int to the first element in this array and move this forward by
i ints.

Correct! :)
Of course this is a BIG FAT nasal demon if used for the above
code with j > 0. pa would have access to only one such array element
of type "array 3 of int".

Yes, because in this case, "pa" pointed to the first of one
array object (whose element type was "array 3 of int"). But
we could point "pa" at the first element of a larger array:

int bigger_array[N][3];

pa = bigger_array; /* same as &bigger_array[0] */

and now we can access all N arrays via pa[j], where j is at
least zero and less than N (and i is at least 0 and less than 3).

The upper bound for j, in pa[j], depends on the object to
which "pa" points. If one is not sure which object that is,
one cannot be sure what the limit for j is. This is the same
problem one might have with:

void work_with_buffer(char *buf) {
/* code here to read from and/or write to buf */
}

What is the maximum value of "i" for which accessing buf is a
valid operation? As with j in pa[j], we cannot tell what that
upper limit is without additional information: perhaps "pa points
to one object", in which j must be 0, or perhaps "pa points to the
first of N objects", in which j can be anything in [0..N). The
valid values for i in buf depend on how many "char" objects buf
"points to the first of". A valid, non-NULL C pointer always points
to the first of some number of objects, but the number is not
(necessarily) carried around with the pointer. (An implementation
is allowed to carry it around, for debugging purposes for instance,
but most do not bother.) If the pointer points to just one object,
the only valid subscript is 0, and you can write either *p or p[0]
to get at that one object. If the pointer points to the first of
ten, you can write either *(p+k) or p[k], as long as k is in [0..9].
 
C

Chris Torek

In said:
... A valid, non-NULL C pointer always points to the first of
some number of objects ...

Oops, this is a slight mis-statement.

If p points to the first of 3 valid objects, then p+1 points to
the second of three. I can claim I also meant "p+1 now points to
the first of two", but I did not quite mean that. Worse, if p
points to the first of 3 valid objects, "p+3" is a valid, non-NULL
C pointer that points to no object at all -- instead, it points
"one past the last" of the three objects, so that p[-3], p[-2],
and p[-1] are all valid operations.
 
B

Barry Schwarz

Hi,
Here is a piece of code

#include<stdio.h>
main()
{
int arr[3]={1,0,2};
int **dp;
int (*pa)[3];
int i = 0;
pa = &arr;
dp = arr;

This is a syntax error. Are there any restrictions on the code the
compiler generates for a syntax error?
printf("%u \t %u \n",arr,&arr); ---> stmt A

arr is the name of an array. In this context, it is automatically
converted to &arr[0]. This invokes undefined behavior since %u tells
printf the corresponding value is an unsigned int while the actual
argument has type int*.

&arr has type int(*)[3]. It invokes undefined behavior for the same
reason.

If you want to print out addresses, you need to use %p and cast the
addresses to void*.
printf("%u \t %u \n",pa,*pa); ----> stmt B

Same problem with both of these arguments.
printf("%u \t %u \n",dp,*dp); -----> stmt C

The same problem with dp. *dp also invokes undefined behavior since
it has type int and %u is expecting type unsigned int.
for(;i<3;i++)
{
printf("arr %d\t pa %d\t dp %d\n",*(arr+i),*(*pa+i),*(dp+i)); -->stmt D
}
}

here is the output

2062498376 2062498376
2062498376 2062498376
2062498376 1
arr 1 pa 1 dp 1
arr 0 pa 0 dp 0
arr 2 pa 2 dp 2

Whats bugging me here is
a) why both the values in stmt A are equal.
b) why both the values in stmt B are equal
c) why the values in stmt C are not equal
d) why shouldn't I use *(pa+i) instead of *(*p+i) in stmt D

pa is a pointer to an array of three int. Its value is the address of
arr which IS an array of three int. When i is 0, (pa+i) evaluates to
(pa). *pa evaluates to an array of three int which in this context is
converted automatically to the address of the first int in the array.
It has type int* which is not suitable for printing with %d and is
also not the value you are looking for.

When is 1, (pa+i) evaluates to the address of the first array of three
int beyond the end of arr. This array does not exist. *(pa+i)
attempts to dereference this value and immediately invokes undefined
behavior.
e) how come *(dp+i) works fine in stmt D

Bad luck. dp is an int**. It appears to contain the address of arr.
It also appears that on your system, and int and an int* both have the
same size and representation. (dp+i) evaluates to the address of the
i-th int* following the one dp points to. It turns out to be the
address of the i-th int after the int dp actually points to. This
invokes undefined behavior because the value will have type int* and
%d is not suitable for pointer values.

I say bad luck because all your undefined behaviors have the
appearance (and only the appearance) of working correctly. This
traditionally lasts until you give a demo to senior management or an
important customer. Good luck would be for your program to terminate
immediately during testing with some error condition that would point
you to the area where the code had undefined behavior.


<<Remove the del for email>>
 

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
474,102
Messages
2,570,645
Members
47,247
Latest member
GabrieleL2

Latest Threads

Top