2

When making the following code:

#include <general.dep>

typedef struct {
} obj;

int f1(obj **o) {
    return 0;
}

int f2(obj *const *o) {
    return 0;
}

int f3(const obj **o) {
    return 0;
}

int f4(const obj *const *o) {
    return 0;
}

int main() {
    obj **o;
    f1(o);
    f2(o);
    f3(o);
    f4(o);
    return 0;
}

I get the following warning messages:

gcc t1.c -I/home/hamidi/code/lib/general/inc -fmax-errors=1 -lgeneral -llzma -pthread -o t1
t1.c: In function ‘main’:
t1.c:26:6: warning: passing argument 1 of ‘f3’ from incompatible pointer type [-Wincompatible-pointer-types]
26 |   f3(o);
   |      ^
   |      |
   |      obj ** {aka struct <anonymous> **}
t1.c:14:20: note: expected ‘const obj **’ {aka ‘const struct <anonymous> **’} but argument is of type ‘obj **’ {aka ‘struct <anonymous> **’}
14 | int f3(const obj **o) {
   |        ~~~~~~~~~~~~^
t1.c:27:6: warning: passing argument 1 of ‘f4’ from incompatible pointer type [-Wincompatible-pointer-types]
27 |   f4(o);
   |      ^
   |      |
   |      obj ** {aka struct <anonymous> **}
t1.c:18:26: note: expected ‘const obj * const*’ {aka ‘const struct <anonymous> * const*’} but argument is of type ‘obj **’ {aka ‘struct <anonymous> **’}
18 | int f4(const obj *const *o) {
   |        ~~~~~~~~~~~~~~~~~~^

How can I resolve these warning issues?

Update

I tagged the functions which issue the warning below:

void f0(obj **ppo) {}
void f1(const obj **ppo) {}  //
void f2(obj const **ppo) {}  //
void f3(obj *const *ppo) {}
void f4(obj **const ppo) {}
void f5(const obj const **ppo) {}  //
void f6(const obj *const *ppo) {}  //
void f7(const obj **const ppo) {}  //
void f8(obj const *const *ppo) {}  //
void f9(obj const **const ppo) {}  //
void fa(obj *const *const ppo) {}
void fb(const obj const *const *ppo) {}        //
void fc(const obj const **const ppo) {}        //
void fd(const obj *const *const ppo) {}        //
void fe(obj const *const *const ppo) {}        //
void ff(const obj const *const *const ppo) {}  //

I'm not sure which ones are equivalents. It seems that f2 and f3 are different, because f2 issues the warning and f3 doesn't. So, may we say that obj const * differs from obj *const?

Update 2

A const for an obj maybe put before or after it, but for pointers it should be put after them. So, let's put const for both after them. Now my question is that in the following code:

void f(int const* const* ppi) {
    //**ppi = **ppi;
    //*ppi = *ppi;
    ppi = ppi;
}

void g(int const* pi) {
    //*pi = *pi;
    pi = pi;
}

int main() {
    int i, *pi = &i, **ppi = &pi;
    f(ppi);
    g(pi);
    return 0;
}

I call two functions, f & g. Both functions prototypes indicate that the argument's content is not altered. g doesn't change the content of the pointer and f doesn't change the content of pointer and the content of where the pointer's content points to.

Now my question is that why does the compiler issue this warning only when f is called, not g?! What should be prototype for f if it wants to say that I'm not going to change the content of the pointer and the content of where it points?

warning: passing argument 1 of ‘f’ from incompatible pointer type [-Wincompatible-pointer-types]
   16 |   f(ppi);
      |     ^~~
      |     |
      |     int **
t2.c:3:26: note: expected ‘const int * const*’ but argument is of type ‘int **’
    3 | void f(int const* const* ppi) {
      |        ~~~~~~~~~~~~~~~~~~^~~
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
hamidi
  • 1,611
  • 1
  • 15
  • 28
  • Yes. `const obj const`, `const obj` and `obj const` are all the same. When `*` turns into `**` the compiler issues the warning which says that the entity which the pointer points to differs in the caller and the called functions, while if this is an issue, this should be an issue also when `*` is used. My question is exactly this. What's the difference between these two, `*` and `**`. For what the issue is not reported when I use `*`? – hamidi Aug 29 '21 at 13:03
  • `are all the same` yes. `When * turns into **` I do not understand that part. `* and **` These are just stars. `what the issue is not reported when I use *?` Because language was created that way and allows that. https://port70.net/~nsz/c/c11/n1570.html#6.5.16.1p1 and https://port70.net/~nsz/c/c11/n1570.html#6.5.16.1p6 and https://port70.net/~nsz/c/c11/n1570.html#note112 and – KamilCuk Aug 29 '21 at 13:16
  • Which language are you using? Note C and C++ are different languages. In C++ an argument of type `int **` is compatible with a parameter of type `int const* const*`, in C it is not. The tag `gcc` pertains to the GNU Compiler Collection, which contains a C compiler, a C++ compiler, and more. – n. m. could be an AI Aug 29 '21 at 13:54
  • Pure C. I use g++ for compiling C++. – hamidi Aug 29 '21 at 14:05
  • Removed irrelevant tags, added the relevant tag – n. m. could be an AI Aug 29 '21 at 14:13

2 Answers2

1

UPDATE: This answer was updated to fix the errors discussed in the comments

The way const works when used with pointers is a little subtle.

const obj* and obj const* means that the pointer points to a const obj type while obj* const means that the pointer is const and points to a non-const obj type.

Using this logic const obj** is equivalent to obj const** which is a non-const pointer that points to a non-const pointer pointing to a const obj while obj* const* is a non-const pointer that points to a const pointer pointing to a non-const obj. And obj** const is a const pointer pointing to a non-const pointer pointing to a non-const obj.

C++ can implicitly cast non-const types to const types. You can see this in the call f2(o) which implicitly converts obj* to obj* const.

On the other hand, C++ has to be careful when implicitly converting multilevel pointers. If C++ allowed for obj** to be implicilty converted into const obj** the following code would be allowed:

const int x = 0;             //x should never be modified
int* xPtr;
const int** xPtrPtr = &xPtr; //would be legal if C++ allowed int** -> const int** conversion
*xPtrPtr = &x;
*xPtr = 1;                   //This changes the value of x!

For this reason GCC issues an error when calling f3(o) because it implicitly casts a obj** to const obj**.

You can read the full set of requirements for for multilevel pointer conversions here

I don't know what you need your code to do, but if you are going to modify the underlying obj or obj* in f3 you should change its argument to be obj** const. If you don't want to modify the underlying obj but modify the obj* you should instead use obj const** as an argument. And if you don't want to modify either obj or the obj* you should use obj const* const*

  • Thanks for the description. Indeed, I need to ensure callers that the pointer is treated as const. I mean content not the pointer itself. If the called function changes the pointer, it doesn't affect on the value of the pointer in the caller function. So, I mean the content. Hence how can I define the function? – hamidi Aug 24 '21 at 12:40
  • @hamidi You should use `obj const**` or `obj const*const*` – Samuel Gonzalez Aug 24 '21 at 12:48
  • @hamidi Alternatively you could use: `typedef const obj* objPtr;` and `int f3(const objPtr* o) { ... }` which is equivalent to `int f3(obj const*const* o)` but a lot easier to read – Samuel Gonzalez Aug 24 '21 at 12:55
  • I think you did a mistake in the second paragraph. `const obj` is equal to `const obj const` and `obj const`. We may put const before or after obj and it doesn't differ in the meaning. But const before * and after * cause two different meanings. Since const for pointers may come only after them, let's put const after obj too. Now I review my question. For this, I update it. Please review it. – hamidi Aug 28 '21 at 13:33
  • `const obj* means that the pointer points to a const obj type while obj const*`. `const obj*` and `obj const*` mean the same. – KamilCuk Aug 28 '21 at 14:27
1

A pointer points to some data. The pointer itself can be const or not. The data can be const or not.

There is no difference between const T and T const (at the top level).

A pointer to data can be implicitly converted to a pointer to const data.

int * a;
const int * b = a; // ok

But the property is not transitive - as in, it does not apply when data itself is a pointer. A pointer to a pointer to data can be converted to a pointer to a const pointer to data.

int * * c; // pointer to a pointer to data
int * const * e = c; // ok, e is a pointer to a const pointer to data

But a pointer to a pointer to data can't be implicitly converted to a pointer to a pointer to const data.

int * * c; // pointer to a pointer to data
const int * * d = c; // err

How can I resolve these warning issues?

Use a cast to cast the pointer to the proper type.

I'm not sure which ones are equivalents.

Qualifiers of arguments do not matter in function declarations. See https://en.cppreference.com/w/cpp/language/function part Top-level cv-qualifiers are dropped from the parameter type .... The qualifiers are visible in function definition, as for example const argument can't be modified.

Let's group the functions definitions, hope I got them right.

void f0(obj **ppo) {}

void f4(obj **const ppo) {}

// same
void f1(const obj **ppo) {}
void f2(obj const **ppo) {}
void f5(const obj const **ppo) {}
// same declarations with above
void f7(const obj **const ppo) {}  // same with f9
void f9(obj const **const ppo) {}
void fc(const obj const **const ppo) {}

// same declarations
void f3(obj *const *ppo) {}
void fa(obj *const *const ppo) {}

// same
void f6(const obj *const *ppo) {}
void f8(obj const *const *ppo) {}
void fb(const obj const *const *ppo) {}
// same declarations with above
void fe(obj const *const *const ppo) {} // same
void fd(const obj *const *const ppo) {}
void ff(const obj const *const *const ppo) {}

Note that specifying multiple same qualifiers is valid in C, is invalid in C++.

may we say that obj const * differs from obj *const?

Yes - one is a const pointer to mutable obj, the other is a mutable pointer to a const obj.

why does the compiler issue this warning only when f is called

As above explained, the const property is not transitive. T ** pointer can't be implicitly converted to a const T** pointers.

, not g?!

But a T* pointer can be converted to a const T* pointer. Just like a T **** pointer can be converted to T *** const * pointer. Like the last * matters.

What should be prototype for f if it wants to say that I'm not going to change the content of the pointer and the content of where it points?

It should stay as it is. Some consider it a language limitation that T** can't be converted to const T** implicitly. Just cast the pointer in such cases. For the limitation in real life, see man 3p execve the RATIONALE section the part The statement about argv[] and envp[] being constants is ..... For completeness, I will copy the table:

Comptibility of dst=src:
┌────────────────────┬──────────┬────────────────┬───────────────┬─────────────────────┐
│               dst: │ char **  │ const char * * │ char *const*  │ const char *const*  │
├────────────────────┼──────────┼────────────────┼───────────────┼─────────────────────┤
│src:                │          │                │               │                     │
│char **             │  VALID   │       —        │     VALID     │          —          │
│const char **       │    —     │     VALID      │       —       │        VALID        │
│char * const *      │    —     │       —        │     VALID     │          —          │
│const char *const*  │    —     │       —        │       —       │        VALID        │
└────────────────────┴──────────┴────────────────┴───────────────┴─────────────────────┘

I also found: Why isn't it legal to convert "pointer to pointer to non-const" to a "pointer to pointer to const" pointer to array not compatible to a pointer to 'const' array? Pointer to array with const qualifier in C & C++ .

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 2
    Thanks for the complete answer. `Some consider it a language limitation that T** can't be converted to const T** implicitly.` I think the same. I think the const transition should take place. Every function should be able to define what parts of the pointers it won't change. If you have an int, every function should be able to explicitly define that it won't change its value. It should be able to define what parts it won't change including the int value itself. The warning is not reasonable by my opinion. – hamidi Aug 29 '21 at 13:22
  • I think you did a bit mistake. f3, fa and fb differ. in fa you can't change ppo, while if f3 you can. In fb, you get the warning, while in the two others you don't. – hamidi Aug 29 '21 at 13:28
  • So the conclusion is that the gcc issues the warning irreasonably and should be changed. Am I incorrect in this conclusion? – hamidi Aug 29 '21 at 13:30
  • `think you did a bit mistake. f3, fa and fb differ` Yes, hopefully fixed. `gcc issues the warning irreasonably and should be changed. Am I incorrect in this conclusion` No, that's very incorrect. Gcc follows the language specification and properly diagnoses invalid language construct. – KamilCuk Aug 29 '21 at 13:42