Является ли верхний уровень изменчивым или значимым в прототипе функции?
Есть ли практическая разница между следующими прототипами?
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() не перекрываются.
Так что просто используйте их, когда они актуальны, и не используйте их иначе.