Passing pointers to the same address as function arguments

153 views Asked by At

Suppose I have a weired add function which does an addition and an increment then stores the result to somewhere else.

void add(const int* a, const int* b, int* c) {
    *c = (*a)++ + *b;
}

If I pass the same pointer to a and c,

int a = 1, b = 3;
add(&a, &b, &a);

Then it will eventually modify the value pointed by a.

My questions:

  1. Does this violate the const qualifier for the first argument?
  2. Is the operation undefined as the typical unsequenced x = x++ is?

I feel the answer to both questions should be YES, but my gcc gives a warning for neither.

EDIT: I know there can be a better workaround for the sake of the code. My question is more on the language feature itself.

3

There are 3 answers

3
ikegami On BEST ANSWER

You have

void add(const int* a, const int* b, int* c) {
    *c = (*a)++ + *b;
}

This doesn't compile.

<source>:2:14: error: increment of read-only location '*a'
    2 |     *c = (*a)++ + *b;
      |      

We could remove the const.

void add(int* a, const int* b, int* c) {
    *c = (*a)++ + *b;
}

But then we encounter undefined behaviour when a == c || a == b. We can signal this using the restrict keyword.

void add(int* restrict a, const int* restrict b, int* restrict c) {
    *c = (*a)++ + *b;
}

With this signature, it becomes UB to call add with two identical arguments.

7
John Bollinger On
  1. Does this violate the const qualifier for the first argument?

Yes.

(*a) is an lvalue expression with type const int. But

The operand of the postfix increment or decrement operator [...] shall be a modifiable lvalue.

(C17 6.5.2.4/1)

and

A modifiable lvalue is an lvalue that [...] does not have a const-qualified type [...]

(C17 6.3.2.1/1)

Thus, (*a)++ expresses a modification that, because of const-qualification, violates language requirements. I take this to be what you mean by "violate the const qualifier". The program has undefined behavior as a result.

That *a and *c are aliases does not enter into it, however. This rule is based on the types of lvalues by which access is expressed, not the effective type of the objects to which they refer, so the assignment to *c is ok as far as these particular provisions are concerned.


  1. Is the operation undefined as the typical unsequenced x = x++ is?

Yes, in the particular function call presented.

The rule is:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

(C17 6.5/2)

Note in particular that this provision is given in terms of the objects that are accessed, not the lvalues through which the accesses are performed.


my gcc gives a warning for neither

That's odd.

6.5.2.4/1 is a language constraint, so diagnosing violations is a conformance requirement on C implementations. My GCC does diagnose this:

$ gcc -o ub -O2 ub.c
ub.c: In function ‘add’:
ub.c:3:14: error: increment of read-only location ‘*a’
     *c = (*a)++ + *b;
              ^~

Something is wrong if yours does not.

On the other hand, 6.5/2 is not a language constraint, and in practice, it may not be possible for the compiler to recognize the UB in such a case. Implementations are not required to diagnose this. I did not find any combination of options that elicited a diagnostic from GCC about this one.

0
gulpr On

Does this violate the const qualifier for the first argument?

It does and it will not compile (https://godbolt.org/z/Mfjf6bsMq), but no UB as there will be no executable to behave defined or undefined way. But if you modify your program to pass the reference to the const object

void add(int* a, const int* b, int* c) {
    *c = (*a)++ + *b;
}

int main(void)
{
    const int a = 5;
    int b = 3, c = 4;

    add(&a, &b, &c);
}

It will invoke Undefined Behavior.

Is the operation undefined as the typical unsequenced x = x++ is?

It can only be undefined if you pass the reference to the same object:

void add(int* a, const int* b, int* c) {
    *c = (*a)++ + *b;
}

int main(void)
{
    int a = 5;
    int c = 4;

    add(&a, &a, &c);
}