6

Following yesterday's question, I experimented some more with pointers. Specifically pointers of type int (*) [n]

Here's some code I wrote :

#include <stdio.h>

int main(void)
{
    int a[5] = {1, 2, 3, 4, 5};

    int (*p) [5] = &a;
    int *q = a;

    printf("\t\t\t\t\tp\t\tq\n\n");
    printf("Original Pointers: \t%20d%20d", p, q);
    printf("\n\n");
    printf("Incremented Pointers:\t%20d%20d", p+1, q+1);
    printf("\n\n");
    printf("Pointer values:         %20d%20d", *p, *q);
    printf("\n\n");

    return 0;
}

And here is it's output:

                                    p                 q

Original Pointers:             132021776           132021776

Incremented Pointers:          132021796           132021780

Pointer values:                132021776                   1
  • The pointer p, jumps by 20 when incremented. Is this because it's a pointer of type int(*)[5], and therefore jumps by sizeof(int) * number of columns in the array ?

  • Both p and q have the same values (but different types), and yet when using the indirection operator with p, I don't get the value at the first cell of the array, instead I get the value of p itself printed out. Why is this?

  • When I use int *p = &a, it throws up a warning (because of different types of pointers, but later when I use the indirection operator with p, I can get it to print the value of the first cell in the array. Is this because when I assign &a to p, it CONVERTS the type of &arr (which is int ( * ) [5]) to the type int *, and then assigns to p?

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
Nathu
  • 449
  • 1
  • 4
  • 11
  • 1
    Your code has undefined behavior; you must print addresses with the `%p` specifier, and cast the argument to `void *`. – ad absurdum Jun 14 '17 at 07:23
  • side note: don't use `%d` for printing pointers, an `int` can be too small for a pointer (and it *is* on typical 64bit systems) –  Jun 14 '17 at 07:24
  • gcc gives very poor diagnostics for simple assignment constraint violations. Make sure to compile with `gcc -std=c11 -pedantic-errors` to make it behave properly. – Lundin Jun 14 '17 at 08:02
  • @Lundin weirdly using the -pedantic-errors option with my gcc (version 4.9.2) isn't showing any error when I use %d to print a pointer. How do I fix this ? – Nathu Jun 14 '17 at 08:31
  • @NathuramGoatse I think it gives a warning for that regardless of `-pedantic-errors`. Just tested in version 4.9.1: `warning: format '%d' expects argument of type 'int', but argument 2 has type 'int *' [-Wformat=]`. I get this warning regardless of if I add `-Wall -Wextra -pedantic-errors` or not. – Lundin Jun 14 '17 at 08:52
  • @Lundin, shows no errors in mine. However -Wall seems to do the trick. Thanks. – Nathu Jun 14 '17 at 08:55

3 Answers3

4

The pointer p, jumps by 20 when incremented. Is this because it's a pointer of type int(*)[5], and therefore jumps by sizeof(int) * number of columns in the array ?

Yes.

Both p and q have the same values (but different types), and yet when using the indirection operator with p, I don't get the value at the first cell of the array, instead I get the value OF p printed out. Why is this ?

p points to an array, so with *p, you get to this array. Evaluating an array without indexing gets you a pointer to its first element. Your *p is evaluated as type int * here.

On a side note, * is commonly called the dereference operator. Using different nomenclature can be confusing to others.

When I use int *p = &a, it throws up a warning (because of different types of pointers, but later when I use the indirection operator with p, I can get it to print the value of the first cell in the array. Is this because when I assign &a to p, it CONVERTS the type of &arr (which is int ( * ) [5]) to the type int *, and then assigns to p ?

I had an answer here misreading this as something like int *q = (int *)p which would create an aliasing pointer and likely undefined behavior, so leaving this little hint here just in case someone would get this idea. But what you suggest in this question is just an invalid assignment. Lundin's answer has a complete explanation of that.


There are more problems in your code, your printing of pointers should look like:

printf("Original Pointers: \t%20p%20p", (void *)p, (void *)q);

Pointers can be bigger than int. The need to cast to void * is because printf() is a variadic function. If it was declared to take a void *, the conversion would be implicit, but as it's not, you have to do it yourself.

  • I'm studying C from the K.N.King book (C Programming: A Modern Approach), and it calls * the 'indirection' operator. – Nathu Jun 14 '17 at 07:46
  • Well, it doesn't have an explicit name in the C standard and is described as "*The unary \* operator denotes indirection.*", so there *is* some justification for using that name, but I'd still say it's a bad idea as the operation it performs is typically called *pointer dereference*, so most C programmers will just call it *dereference* –  Jun 14 '17 at 07:52
  • @NathuramGoatse still I'll reword my hint because *indirection* isn't *wrong* –  Jun 14 '17 at 07:53
  • 1
    `int *p = &a` is not an aliasing issue, it is invalid C code. I wrote an answer explaining why. It would have been an aliasing issue if the programmer forced a pointer conversion through a cast. But this isn't the case here. Pointer conversions do not happen implicitly. – Lundin Jun 14 '17 at 07:58
  • @Lundin that's correct, but he could simply insert a *cast* to get around this and the result is the same of an incompatible alias. Still, good catch. Ok, you already wrote the same in your edit.... –  Jun 14 '17 at 08:01
  • @FelixPalmen He would have to write `int *p = (int*)&a;`. Then the code would compile. I don't quite see how it would invoke undefined behavior. Please note that the _effective type_ of the data is `int[5]`, so code accessing an item of that array through an `int*` does not violate strict aliasing. Code such as `int* p = (int*)(bananas_t*)(double*)&a; .. *p = something;` is fine, as far as strict aliasing is concerned. – Lundin Jun 14 '17 at 08:08
  • @Lundin I don't see how `int[]` and `int` should be compatible types? accessing the array through `int *` is of course fine, but there's another pointer of `int(*)[]` involved in this code .... –  Jun 14 '17 at 08:13
  • @Lundin ok, I think I misread the question at this point and was the whole time thinking about something like `int *q = p` ... different thing -- changing this whole section now! –  Jun 14 '17 at 08:19
3

Your code causes undefined behavior, so none of the output can be validated.

First, some generic information :

As per the standard, using mismatched type of arguments with format specifiers causes UB. To print a pointer with printf(), you must use %p format specifier and cast the corresponding argument to void *.

Related, quoting C11, chapter §7.21.6.1/P8

p

The argument shall be a pointer to void. [...]

and P9,

[....] If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

Since printf() is a variadic function and no default argument promotion takes place, the cast to void * is necessary.


Now, coming to the more direct questions.

§1. The pointer p, jumps by 20 when incremented. [...]

You're right, check the data type. a pointer with type as int (*) [5] would be incremented / decremented by sizeof(int [5]), the pointing type, based on your platform. Pointer arithmatic honors the datd type

§2. Both p and q have the same values (but different types), and yet when using the indirection operator with `p [....]

Please note the type there. p is of type int (*) [5], so *p is of type int [5]. That's it. All you should have is an array as a product of the dereference. (But read on.....)

Now, while passing an array type as a function argument, it decays to the pointer to the first element to the array, hence is is analogous to int *, a pointer. So, ultimately, you'll print a pointer value.

§3. When I use int *p = &a, it throws up a warning [....]

Wait. Stop. That is a constraint violation. Strictly speaking, it's invalid C code. The types int * and int (*) [5] are not compatible and there exist no cast (implicit or explicit) which makes this a valid expression. Don't do it, use proper types.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
3

In addition to the answer by Felix:

When I use int *p = &a, it throws up a warning

This is because this code is not valid C. It is a so-called constraint violation (roughly means severe language violation). So the compiler is required to give a diagnostic message. A better compiler would give an error, not a warning.

No pointer conversion takes place. Pointer conversions are not done implicitly in C unless one of the operands is void*.


The reason it isn't valid C is because the expression is not a valid form of simple assignment. Valid forms are listed in C11 6.5.16.1:

6.5.16.1 Simple assignment
Constraints

One of the following shall hold:

— the left operand has atomic, qualified, or unqualified arithmetic type, and the right has arithmetic type;

Not the case here, both operands are pointers.

— the left operand has an atomic, qualified, or unqualified version of a structure or union type compatible with the type of the right;

Not the case here.

— 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 is a (unqualified) pointer type. But the right operand is not a compatible type. So this is condition is not met.

— 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;

No, there is no void pointers here.

— the left operand is an atomic, qualified, or unqualified pointer, and the right is a null pointer constant; or

No null pointer constants either.

— the left operand has type atomic, qualified, or unqualified _Bool, and the right is a pointer.

And no bools here either.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • in the case of a constraint violation, is the behavior undefined (because the code works despite it) ? – Nathu Jun 14 '17 at 09:00
  • @NathuramGoatse Yes such a program is completely undefined by the C standard, as it is no longer a C program but something else. – Lundin Jun 14 '17 at 09:16