Why is implicit pointer of pointer to pointer conversion legal?

226 views Asked by At

I recently came across some code on stackoverflow where pointers to pointers where used to change allocated memory. While checking the code I made the mistake to add an ampersand to a pointer so make a pointer to a pointer still the compiler happily compiled and runtime errors occured. As an example

#include <stdio.h>

void func(int **p) {
  printf("Pointer is at %p\n", p);
}

int main(void) {
  int *p = 0;
  func(p);
  func(&p);
  int **pp = &p;
  func(&pp);
  return 0;
}

I do understand that C has significantly lower restrictions for pointers than C++ and allows something like char *buf = malloc(SIZE) while not allowed in C++. I see it as a convenience because it happens so much in C.

Nevertheless, I think that the amount of references is a great source of errors and I wonder why one might allow this especially since int is different from int*. Furthermore I want to know if the C standard says something about this.

EDIT I compiled it under ideone.com which likely does not show warnings. My local clang compiler as well as gcc do give warnings. Still, why are they just warnings when they represent different things.


PS, I feel like something like this should have been asked in the last six years of SO. If this a duplicate I am sorry for not finding one.

4

There are 4 answers

4
Keith Thompson On BEST ANSWER

The conversion is not legal. More precisely, there is no implicit conversion from int* to int**, or from int*** to int**. Attempting to pass an int* or an int*** to a function that requires an int** argument is a constraint violation; any conforming compiler must diagnose it. (The diagnostic message may be a non-fatal warning.)

When I compile your code with gcc, even with default options (which makes gcc non-conforming), I get several warnings:

c.c: In function ‘func’:
c.c:4:3: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int **’ [-Wformat=]
   printf("Pointer is at %x\n", p);
   ^
c.c: In function ‘main’:
c.c:9:3: warning: passing argument 1 of ‘func’ from incompatible pointer type [enabled by default]
   func(p);
   ^
c.c:3:6: note: expected ‘int **’ but argument is of type ‘int *’
 void func(int **p) {
      ^
c.c:12:3: warning: passing argument 1 of ‘func’ from incompatible pointer type [enabled by default]
   func(&pp);
   ^
c.c:3:6: note: expected ‘int **’ but argument is of type ‘int ***’
 void func(int **p) {
      ^

I don't know why ideone doesn't complain about it (http://ideone.com/uzeXur).

1
too honest for this site On

The compiler apparently has no warnings enabled:

int main()
{
    int *p = 0, **p2 = p;

...

$ gcc -std=c11 test.c -lncurses
test.c: In function ‘main’:
test.c:8:21: warning: initialization from incompatible pointer type [enabled by default]
  int *p = 0, **p2 = p;
                     ^

So, gcc complains very well. That site might have disabled warnings or swallows it. It should do neither.

For gcc, You can also turn all warnings to errors with -Werror or just some specific: -Werror=<name of warning>. For this, it would be strict-aliasing.

Reason this is not default is propably historical reasons. Making it an error by default might break too much legacy or otherwise broken software in the wild.

The reason it does not produce an error might be found here in the standard. Sentence 7. It is only UB if the pointer is not properly aligned for the receiving pointer or dereferenced (which is not done here).

0
Sean On

I think ideone is just soft on the warnings. Without -Wall or any extra warning flags on my computer gcc gives me

test_ptrs.c: In function ‘func’:
test_ptrs.c:4:3: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int **’ [-Wformat=]
   printf("Pointer is at %x\n", p);
   ^
test_ptrs.c: In function ‘main’:
test_ptrs.c:9:3: warning: passing argument 1 of ‘func’ from incompatible pointer type [enabled by default]
   func(p);
   ^
test_ptrs.c:3:6: note: expected ‘int **’ but argument is of type ‘int *’
 void func(int **p) {
      ^
test_ptrs.c:12:3: warning: passing argument 1 of ‘func’ from incompatible pointer type [enabled by default]
   func(&pp);
   ^
test_ptrs.c:3:6: note: expected ‘int **’ but argument is of type ‘int ***’
 void func(int **p) {
0
haccks On

Using wrong specifier to print a data type invoke undefined behavior. Change %x to %p and cast argument of printf to void * and you will have warnings.

Live Demo