-2

I was expecting a garbage value from the first printf() statement but I got the address instead. Why?

#include<stdio.h>
int main()
{
    int (*p)[10];
    int a[10]={1,2,3,4,5,6,7,8,9,0};
    p=a+10;
    printf("%d\n",*(p));
    printf("%u",(p));
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335

3 Answers3

1

Given this declaration:

    int (*p)[10];

p is a pointer to an array. The expression *p therefore designates the pointed-to array itself. But in almost all C contexts, including in particular when it is an argument in a function call expression, the value of an expression of array type is automatically converted to a pointer to the first element of the array. The address of that element is the same as the address of the array itself, so if p is in fact a valid pointer then it is entirely reasonable for

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

to print two identical lines.

Do note, however, the use of the %p format directive instead of %u or %d for printing a (void) pointer value, and the casts to void *. These changes to your original code are necessary for the behavior of the printf calls to be well defined.

It is true that as far as the C language is concerned, *p does not designate an object in your case, so in that sense, evaluating the expression *p produces undefined behavior. This is one of the reasons why I say only that it is "reasonable" for the code to emit two identical lines. In practice, however, implementations are likely to treat the expression *p identically to (int *) p, whose behavior is well-defined in this case.

You should also be aware -- and your compiler should be warning you -- that the expression a + 10 has the wrong type (int *) to be assigned directly to your p (an int (*)[10]). It is allowed to convert between those two types, but a cast is required:

    p = (int (*)[10])(a + 10);

or

    p = (void *)(a + 10);

There is good reason to expect that your compiler is compiling the original assignment expression as if the required cast were present, especially inasmuch as it is a no-op in many implementations, probably including yours, but you should not rely on that.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Fair observation, @Bob__. I have added a few words aimed more directly at that issue. – John Bollinger Aug 14 '19 at 13:56
  • The main issue is rather that `p=a+10;` won't compile in the first place. `a` decays into an `int*`, then +10 is pointer arithmetic on that type. But it isn't possible to assign an `int*` to an `int (*p)[10]`. – Lundin Aug 14 '19 at 14:01
  • @Lundin, evidently it *does* compile for the OP, and I anticipate that it would compile for me, too, albeit with a warning. I'll add something speaking to that issue, but I certainly don't think it's the main one. – John Bollinger Aug 14 '19 at 14:04
  • Even gcc in default gnu crap mode gives " warning #556: a value of type "`int *`" cannot be assigned to an entity of type "`int (*)[10]`"". Constraint violation of simple assignment. The compiler need not generate a working binary and if it does, all bet about what that binary does are off - it is UB. – Lundin Aug 14 '19 at 14:05
  • Yes, @Lundin, it has UB. And that should be corrected. But I maintain that that is not the main issue with the OP's code, neither from the point of view of possible misbehavior (the format mismatches are more likely to have unwanted effects in practice) nor from the point of view of the semantics most likely to be of interest to the OP. – John Bollinger Aug 14 '19 at 14:14
  • Pointer types aside, I wonder if the conversion is ok still. The `int*` is allowed to point 1 `int` beyond the array and does so. The `int(*)[10]` is allowed to point 1 `int[10]` beyond the array. But I don't think anything in the standard allows `int(*)[10]` to point 1 `int` beyond the array. – Lundin Aug 14 '19 at 14:18
  • Relevant part would be C11 6.5.6 additive operators §8 "If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined." – Lundin Aug 14 '19 at 14:19
  • @Lundin, the standard explicitly specifies that object pointers of one type may be converted to object pointers of another type (paragraph 6.3.2.3/7). The only qualification is about alignment, which cannot be an issue for the particular conversion of interest here. Whether it is allowed to dereference the resulting pointer is perhaps an interesting question (I say no), but I see no reason whatever to doubt the validity of the conversion (by cast, or by assignment conversion to `void *`). – John Bollinger Aug 14 '19 at 14:26
  • It is definitely not allowed to de-reference the pointer beyond the array, but you are allowed to do additive pointer arithmetic up to 1 item beyond it. Further additive pointer arithmetic beyond that is explicitly UB per the part I quoted. So what happens if you go 1 beyond first, then do a wild conversion to another type... hmm. – Lundin Aug 14 '19 at 14:33
  • @Lundin, paragraph 6.5.6/8 qualifies the definition of pointer addition and subtraction based on the value of the integer addend / minuend and the array to which, or to just past the end of which, the pointer points. If a pointer does not point to an array element (as determined in part by its derivation) then no behavior is defined for adding a positive value to it. – John Bollinger Aug 14 '19 at 14:43
1

For starters this statement

p=a+10;

is invalid.

The left side of the assignment has the type int( * )[10] while the right side of the assignment has the type int * and there is no implicit conversion from one type to another.

You have to write

p = ( int( * )[10] )( a + 10 );

Secondly these calls of printf

printf("%d\n",*(p));
printf("%u",(p));

have undefined behavior because there are used invalid conversion specifier d and u with arguments of pointer types because *p and p are both pointers.

So a valid program can look the following way

#include <stdio.h>

int main(void) 
{
    int (*p)[10];
    int a[10]={1,2,3,4,5,6,7,8,9,0};

    p = ( int( * )[10] )( a + 10 );

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

    return 0;
}

A pointer may point to the memory that follows the last element of an object (or an array).

So after this statement

p = ( int( * )[10] )( a+10 );

the pointer p points after the first element of an imagined two-dimensional array elements of which in turn one-dimensional arrays of the type int[10] and the first element of the imagined two-dimensional array corresponds to the array a.

The expression *p is also a pointer. Its type is int * because the expression *p gives an lvalue of the type int[10] and in expressions this lvalue is implicitly converted to pointer to its first element (in this case of the type int).

So the both expressions p and *p points to the same memory and have the same value.

This is demonstrated by the program output

0x7ffcfd46ea48
0x7ffcfd46ea48

The difference between these two pointers, p and *p, is the type of an object they potentially point to.

Consider the following demonstrative program

#include <stdio.h>

int main(void) 
{
    int (*p)[10];
    int a[10]={1,2,3,4,5,6,7,8,9,0};

    p = ( int( * )[10] )( a+10 );

    printf("%zu\n",sizeof( *p ) );
    printf("%zu\n",sizeof( **p ) );

    return 0;
}

Its output is

40
4

You could get undefined behavior if you wrote

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

In this case the expression **p has the type int and there is an access to the memory beyond the array a.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

The array can be treated as (decays to) a pointer to the first element.

When you take the address of an array, you generate a pointer to the array, generally to the same address as first element

When you dereference the pointer to array, you get pointer to element, which generally has the same actual numeric value.

I say generally, because some strange C emulator, or some system that puts an extra indirection in all pointer objects, might decide that the array and the content of the array are 2 different objects.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27