1

I have the following code:

typedef struct S1 S1_t;
struct S1
{
  uint8_t *ptr1;
  uint8_t **ptr2;
};

void get_ptr1(const S1_t *s1, uint8_t const **ptr1)
{
  *ptr1= s1->ptr1;
}

void get_ptr2(const S1_t *s1, uint8_t const *const **ptr2)
{
  *ptr2 = s1->ptr2;
}

If I leave it like that, I get the following warning from the GCC compiler for the function get_ptr2:

assignment to ‘const uint8_t * const*’ {aka ‘const unsigned char * const*’} from incompatible pointer type ‘uint8_t **’ {aka ‘unsigned char **’}

In order for me to mute the warning, I have to cast s1->ptr2 before assigning it to ptr2:

void get_ptr2(const S1_t *s1, uint8_t const *const **ptr2)
{
  *ptr2 = (const uint8_t *const *)s1->ptr2;
  return 0;
}

I don't understand why I need to do this cast operation here, when I don't need to do a (const uint8_t *) cast in get_ptr1?

My goal here is to implement getter functions for the structure S1's members, and I want to make sure the function getting those members cannot modify them. Is the compiler throwing a warning at me because I am doing something dangerous here?

I have the -wall and -wextra compilation option flag enabled.

3
  • 1
    How does uint8_t const *const **ptr2 make any sense? What you actually should be doing is probably const uint8_t* get_ptr1 (const S1_t* s1); But exposing private members through pointers is often bad design in the first place, const or not. Commented Jan 26, 2024 at 15:02
  • @Lundin it's a simplified version of my code. In reality, I'd have an integer being returned here, representing an error code in case one parameter is NULL, or an other value depending on the error type. In my structure, I store a matrix, and I need to access it outside. Won't explain the reason why exactly, would take too long, and I only have 250 char left. Commented Jan 26, 2024 at 15:07
  • 1
    The reason T ** cannot be automatically converted to const T ** is explained here. This also prevents automatic conversion to const T * const *. Commented Jan 26, 2024 at 15:13

2 Answers 2

3

While it's allowed to assign a pointer to a non-const type to a pointer to a const type, that only applies to the first "level" of pointer. If this were allowed, it would be possible to allow a const object to be modified.

For example:

const char c = 1;
const char *p1 = &c;
char *p2;
char **pp1 = &p2;
const char **pp2 = pp1;    // invalid char ** to const char ** conversion
*pp2 = p1;                 // p2 now points to c (no warning!)
*p2 = 2;                   // modifying a const object (no warning!), UB

In the above case is slightly different from yours since only the "outer level" is const. If pp2 were defined as const char * const *pp2 then this wouldn't be possible, as the line *pp2 = p1; would contain an error. This is really a defect in the C standard that your case isn't allowed, given that each pointer level in the destination type is const qualified.

Applying a cast, as you've done, is the appropriate fix for this case.

Sign up to request clarification or add additional context in comments.

Comments

0

I don't understand why I need to do this cast operation here, when I don't need to do a (const uint8_t *) cast in get_ptr1.

The C language spec's applicable rule for simple assignment where both operands have pointer types other than pointers to void is:

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 operand has all the qualifiers of the type pointed to by the right operand

In that light, consider first the working alternative:

void get_ptr1(const S1_t *s1, uint8_t const **ptr1)
{
  *ptr1= s1->ptr1;
}

*ptr1 has type uint8_t const *, and its pointed-to type is uint8_t const.

s1->ptr1 has type uint8_t *, and its pointed to type is uint8_t.

Now evaluate the constraint:

  • "both operands are pointers to qualified or unqualified versions of compatible types" -- yes. both are pointers to qualified or unqualified versions of uint8_t.

  • "the type pointed to by the left operand has all the qualifiers of the type pointed to by the right operand" -- yes, trivially, because the type pointed to by the right operand is unqualified.

Now consider the non-working example:

void get_ptr2(const S1_t *s1, uint8_t const *const **ptr2)
{
  *ptr2 = s1->ptr2;
}

*ptr2 has type uint8_t const * const *, and its pointed-to type is uint8_t const *.

s1->ptr2 has type uint8_t **, and its pointed to type is uint8_t *.

Now evaluate the constraint:

  • "both operands are pointers to qualified or unqualified versions of compatible types" -- NO. uint8_t const * const * and uint8_t ** are neither compatible types (which for pointer types means the same type), nor differently-qualified versions of compatible types.

For that, you need to understand that the "differently qualified" applies to the type itself, not to any other types in its derivation. Thus, int * and int * const are differently qualified pointers to int. int * and const int * are identically (un)qualified pointers to different types (int and const int). And as for uint8_t ** and uint8_t * const * const, these are differently qualified pointers to different types.

It's not entirely clear what you're trying to achieve via the const-qualification, but the ptr2 analog of your get_ptr1() would be

void get_ptr2(const S1_t *s1, uint8_t * const **ptr2)
{
  *ptr2 = s1->ptr2;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.