main( )
{
int s[5][2] = {{ 1234, 56 }, { 1212, 33 }, { 1434, 80 }, { 1312, 78 } } ;
int ( *p )[2] ;
int i, j, *pint ;
for ( i = 0 ; i <= 3 ; i )
{
p = &s[i] ;
pint = p ;
//how does integer pointer pint accept the p
printf ( "\n" ) ;
for ( j = 0 ; j <= 1 ; j )
printf ( "%d ", *( pint j ) ) ;
//why cant I print **pint as I can print the output using **p
}
}
CodePudding user response:
For pint = p ;
, the compiler will issue a warning or error message because the types do not match. Then the C standard does not define the behavior, but compilers will, if they compile this code, convert p
to the type of pint
. Then pint
will be an int *
that points to the same place as p
. In particular, it will point to the first element in the array that p
points to.
In printf ( "%d ", *( pint j ) ) ;
, pint
is an int *
, so pint j
calculates the address that is j
further int
along in memory. (The semantics have been muddied by the earlier illicit conversion, but we will suppose this arithmetic works as if pint
is a fully defined pointer to elements in an array, and j
is in bounds.) Then *
references the pointed-to int
. You cannot use **pint
because pint
is a pointer to an int
, not a pointer to a pointer or a pointer to an array.
C uses the type of the expression to determine how to interpret it. It does not use the value or its history to know that pint
came from an int (*)[2]
. pint
was declared to be of type int *
, so that is what it is. In the printf
line, pint
is merely an int *
; it is not treated as an int (*)[2]
.
CodePudding user response:
For pint = p;
, pint
has type int *
and p
has type int (*)[2]
. This is an implicit conversion from one pointer type to another, which is a constraint violation when neither of them is a pointer to void
. C17 6.5.4 (Cast Operators) paragraph 3 (under Constraints) says:
Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast.
6.5.16.1 (Simple Assignment) paragraph 1 (under Constraints) lists the constraints on the types for each side of a simple assignment operation:
One of the following shall hold:114)
- the left operand has atomic, qualified, or unqualified arithmetic type, and the right has arithmetic type;
- the left operand has an atomic, qualified, or unqualified version of a structure or union type compatible with the type of the right;
- the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
- the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of
void
, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;- the left operand is an atomic, qualified, or unqualified pointer, and the right is a null pointer constant; or
- the left operand has type atomic, qualified, or unqualified
_Bool
, and the right is a pointer.
The simple assignment pint = p;
is not allowed by those constraints. The compiler is required to produce at least one diagnostic message when compiling this code.
The constraint violation can be avoided by using an explicit cast: pint = (int *)p;
. This conversion is allowed by 6.3.2.3 (Pointers) paragraph 7:
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned69) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result 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.
A pointer to an array has the same alignment requirement as a pointer to an element of the array, as indicated by 6.5.3.4 (The sizeof
and _Alignof
operators) paragraph 3:
The
_Alignof
operator yields the alignment requirement of its operand type. The operand is not evaluated and the result is an integer constant. When applied to an array type, the result is the alignment requirement of the element type.
So the conversion (int *)p
is allowed. However, the C standard does not describe what you can do with such a pointer other than convert it back to the original type. Since an array starts at the same address as its initial element, for a typical C compiler, (int *)p
(where p
is a pointer to an object with array type) will evaluate to a pointer to the initial element of the array. That is not guaranteed by the C standard, so it is better to avoid code like that.
The programmer's intention with pint = (int *)p;
is presumably to get a pointer to the initial element of the array object pointed to by p
. A perfectly legitimate way to do that is to do pint = *p;
or pint = p[0];
Since p
has type int (*)[2]
then *p
has type int [2]
(array length 2 of int
). By 6.3.2.1 (Lvalues, arrays, and function designators) paragraph 3, it is converted to a pointer to the initial element of the array:
Except when it is the operand of the
sizeof
operator, or the unary&
operator, or is a string literal used to initialize an array, an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
Colloquially, the array is said to "decay" to a pointer to its initial element.
Now to answer the specific questions:
-
pint = p ; //how does integer pointer pint accept the p
The compiler does not need to fully accept it and needs to produce a diagnostic message. The behavior is undefined by omission of any explicit definition of required behavior.
-
//why cant I print **pint as I can print the output using **p
"The operand of the unary
*
operator shall have pointer type" (6.5.3.2 paragraph 2).pint
has typeint *
so*pint
is OK.*pint
has typeint
. The leftmost*
of**pint
is operating on thatint
value so is not OK.p
has typeint (*)[2]
so*p
is OK.*p
has typeint [2]
which decays to anint *
pointing to the initial element of theint [2]
. The leftmost*
of**p
is operating on thatint *
value so is OK.