Является ли верхний уровень изменчивым или значимым в прототипе функции?

Есть ли практическая разница между следующими прототипами?

void f(const int *p);

void f(const int *restrict p);

void f(const int *volatile p);

В разделе C11 6.7.6.3/15 (последнее предложение) говорится, что квалификаторы верхнего уровня не рассматриваются для целей определения совместимости типов, т.е. разрешено, чтобы определение функции имело другие квалификаторы верхнего уровня в своих параметрах, чем в прототипе декларация имела.

Однако (в отличие от C++) это не говорит о том, что они полностью игнорируются. В случае const это явно спорный вопрос; Однако в случае volatile а также restrict может быть, может быть разница.

Пример:

void f(const int *restrict p);

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

Имеет ли присутствие restrict в прототипе позволяют компилятору оптимизировать чтение a за return a;?

( Связанный вопрос)

4 ответа

Если в стандарте ничего нет, то дело за компиляторами, но кажется, что по крайней мере для gcc 4.9 (для x86) они игнорируются. Проверьте этот небольшой фрагмент, который я использовал, чтобы дразнить компилятор:

static int b;

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

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

Если я скомпилирую это как есть, я получу

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

Если я скомпилирую его, используя void f(const int *__restrict__ p), я получу

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

И наконец, если я скомпилирую его, используя void f(const int *__volatile__ p), я получу

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

Похоже, что на практике они игнорируются и в Си.

Предполагая определение f не хватает restrict квалификатор, код должен быть четко определен. C11 (n1570) 6.5.2.2 (вызовы функций) p7 [emph. мой, идентичная формулировка в C99 TC3 (n1256)]

Если выражение, обозначающее вызываемую функцию, имеет тип, который включает в себя прототип, аргументы неявно преобразуются, как если бы посредством присваивания, в типы соответствующих параметров, принимая тип каждого параметра за неквалифицированную версию объявленной тип.

Функция f вызывается с неквалифицированными аргументами (и, следовательно, с аргументами правильных типов), и все его объявления имеют совместимый тип (согласно кавычке в вопросе): вызов функции четко определен. (Если в стандарте нет ничего, делающего это явно неопределенным. Я не думаю, что это так.)

Наличие на высшем уровне volatile Квалификатор, примененный к параметру в определении функции, может привести к тому, что поведение будет определено в некоторых случаях, если это не так. Наиболее заметно:

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

Если x не были объявлены volatile Компилятор может оптимизировать x++ а также x-- поскольку можно предположить, что никакой другой код никогда не будет проверять значение x между этими двумя операциями. volatile объявление, однако, заставит компилятор предполагать, что код, который проверяет x после setjmp может выполняться между x++ а также x-- и, таким образом, соблюдать значение x проводится в то время.

Может быть возможно придумать соглашение о вызовах платформы, где "умный" оптимизатор, который ничего не знал об определении функции, кроме того факта, что она не использовала volatile Спецификатор аргумента будет способен генерировать код, который будет недопустим при наличии такого классификатора, но даже на такой платформе, компилятор, который видит только то, что в прототипе функции отсутствует volatile Спецификатор не будет иметь оснований предполагать, что его определение не будет включать его.

Использование 'volatile' для параметра означает повторное чтение параметра каждый раз, когда он используется, а не просто использование какого-либо ранее прочитанного значения.

это "обычно" бесполезно для переданного параметра.

Время для "volatile" - это когда что-то может асинхронно измениться при выполнении кода, например, что-то, измененное в значении прерывания или ввода-вывода.

Переданные параметры являются копиями и не изменяются асинхронно.

"Restrict" - это обещание кодера компилятору, что компилятор может игнорировать некоторые возможные проблемы,

например, я, кодер, обещаю, что области памяти этого вызова memcpy() не перекрываются.

Так что просто используйте их, когда они актуальны, и не используйте их иначе.

Другие вопросы по тегам