Is top-level volatile or restrict significant in a function prototype?
Asked Answered
B

4

4

Is there any practical difference between the following prototypes?

void f(const int *p);

void f(const int *restrict p);

void f(const int *volatile p);

The section C11 6.7.6.3/15 (final sentence) says that top-level qualifiers are not considered for the purposes of determining type compatibility, i.e. it is permitted for the function definition to have different top-level qualifiers on its parameters than the prototype declaration had.

However (unlike C++) it does not say that they are ignored completely. In the case of const this is clearly moot; however in the case of volatile and restrict maybe there could be a difference.

Example:

void f(const int *restrict p);

int main()
{
     int a = 42;
     const int *p = &a;
     f(p);
     return a;
}

Does the presence of restrict in the prototype allow the compiler to optimize out the read of a for return a; ?

(Related question)

Bellhop answered 2/3, 2015 at 21:35 Comment(5)
I don't think that the last sentence that you are referring to applies to the whole paragraph 15, it only applies to the phrase just preceding it. At the beginning it clearly states corresponding parameters shall have compatible types, so qualification of the parameters matter to determine the prototype of the function.Linares
@JensGustedt so you are saying that void f(int); void f(int const); int main() {} is ill-formed?Bellhop
Ignoring common sense, the only thing somehow making a restrict-less definition UB I currently can find is 6.2.7 p2 All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined. But this is out-right absurd: It would be equally applicable to const (say, an ABI using a different way to pass const arguments) and it would make void f(const int *restrict); different, as there is no p which is declared in the first place (so there is no declaration).Terena
i have a good impression that in standard, the type of p will be promote to const int *restrict automatically since they are compatible. i think it has the effect of optimization, since in the function body, the type of p is indeed const int * restrict. i think memcpy is a good example: manpagez.com/man/3/memcpy.Ennius
@HuStmpHrrr: As I currently see it, the compiler cannot assume the top-level qualifiers of the parameters in the prototype match the definition (except for library functions, where the compiler is "allowed" to know what they do). That we write down restrict qualifiers in documentation if and only if they are present in the definition is, as it currently seems, just a conventional thing addressing humans, not the compiler.Terena
G
1

If there is nothing in the standard, then it's up to the compilers, but it seems that at least for gcc 4.9 (for x86) they are ignored. Check this small snippet that I've used to tease the compiler:

static int b;

void f(const int *p) {
  b = *p + 1;
}

int main()
{
     int a = 42;
     const int *p = &a;
     f(p);
     return a;
}

If I compile it as is, I get

f(int const*):
    pushq   %rbp
    movq    %rsp, %rbp
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movl    (%rax), %eax
    addl    $1, %eax
    movl    %eax, b(%rip)
    popq    %rbp
    ret
main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    $42, -12(%rbp)
    leaq    -12(%rbp), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, %rdi
    call    f(int const*)
    movl    -12(%rbp), %eax
    leave
    ret

If I compile it using void f(const int *__restrict__ p) I get

f(int const*):
    pushq   %rbp
    movq    %rsp, %rbp
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movl    (%rax), %eax
    addl    $1, %eax
    movl    %eax, b(%rip)
    popq    %rbp
    ret
main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    $42, -12(%rbp)
    leaq    -12(%rbp), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, %rdi
    call    f(int const*)
    movl    -12(%rbp), %eax
    leave
    ret

Anf finally if I compile it using void f(const int *__volatile__ p) I get

f(int const*):
    pushq   %rbp
    movq    %rsp, %rbp
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movl    (%rax), %eax
    addl    $1, %eax
    movl    %eax, b(%rip)
    popq    %rbp
    ret
main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    $42, -12(%rbp)
    leaq    -12(%rbp), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, %rdi
    call    f(int const*)
    movl    -12(%rbp), %eax
    leave
    ret

So it seems that in practice they are ignored in C as well.

Goodell answered 2/3, 2015 at 21:59 Comment(2)
This is a pure language-lawyer question. What compilers currently do and what they might do at some point in the future, are two different things.Terena
But your examples area all for function definitions, so it doesn't answer the question.Sweptwing
T
1

Assuming a definition of f lacking the restrict qualifier, the code should be well-defined. C11 (n1570) 6.5.2.2 (Function calls) p7 [emph. mine, identical wording in C99 TC3 (n1256)]

If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type.

The function f is called with unqualified arguments (and thus, with arguments of the correct types), and all its declarations are of compatible type (as per the quote in the question): The function call is well-defined. (If there isn't anything in the standard making it explicitly undefined. I don't think there is.)

Terena answered 3/3, 2015 at 18:8 Comment(4)
The example code is well-formed but the question is whether the optimizer can assume that f(p); does not modify a.Bellhop
I think your quote might go towards answering the question though (pointing out that qualifiers are removed in making the function call).Bellhop
@MattMcNabb: I thought the only thing to show is whether the definition of f must also have its parameter declared restrict, not if f could modify *p. You're right in that the latter isn't entirely clear from my answer. I'll mull over this again...Terena
If a function prototype declares a parameter as const * restrict but the function definition does not include a restrict qualifier, would the const * restrict in the prototype limit what the function would be allowed to do without invoking UB?Fleck
F
0

The presence of a top-level volatile qualifier applied to a parameter in a function's definition may cause behavior to be defined in some cases where it otherwise would not. Most notably:

int test(int volatile x)
{
  if (setjmp(&someJumpBuff)) return x;
  x++;
  someFunction();  // A function that calls longjmp(&someJumpBuff, 1);
  x--;
  return x;
}

If x were not declared volatile, a compiler could optimize out the x++ and x-- since it could assume that no other code would ever examine the value of x between those two operations. The volatile declaration, however, would force the compiler to presume that code which examines x after the setjmp might execute between the x++ and x-- and thus observe the value x held at that time.

It may be possible to contrive a platform calling convention where a "clever" optimizer that knew nothing about a function's definition beyond the fact that it did not use a volatile qualifier on an argument would be able to generate code that would not be allowable in the presence of such a qualifier, but even on such a platform, a compiler that only saw that a function's prototype lacked a volatile qualifier would have no basis for assuming that its definition wouldn't include one.

Fleck answered 29/8, 2018 at 15:41 Comment(0)
F
-1

using 'volatile' on a parameter means to re-read the parameter each time it is used rather than just using some previously read value.

this is 'usually' useless on a passed parameter.

The time for 'volatile' is when the something can change asynchronously to the code execution, such as something modified in an interrupt or I/O value.

Passed parameters are copies and do not change asynchronously.

'Restrict' is a promise by the coder, to the compiler, that certain possible problems can be ignored by the compiler,

such as 'I, the coder, promise that the memory areas of this call to memcpy() do not overlap.

So just use them when they are relevant and don't use them otherwise.

Fishwife answered 3/3, 2015 at 1:7 Comment(1)
I don't see how this tries to answer the questionBellhop

© 2022 - 2024 — McMap. All rights reserved.