4

以下原型之间有什么实际区别吗?

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显然没有实际意义;但是,在 和 的情况下volatilerestrict可能会有所不同。

例子:

void f(const int *restrict p);

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

原型中的存在是否允许编译器优化forrestrict的读取?areturn a;

相关问题

4

4 回答 4

1

如果标准中没有任何内容,则取决于编译器,但似乎至少对于 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

因此,在实践中,它们似乎在 C 中也被忽略了。

于 2015-03-02T21:59:17.747 回答
1

假设f缺少restrict限定符的定义,代码应该是明确定义的。C11 (n1570) 6.5.2.2(函数调用)p7 [emph. 我的,C99 TC3 (n1256) 中的相同措辞]

如果表示被调用函数的表达式具有包含原型的类型,则参数被隐式转换为相应参数的类型,就像通过赋值一样,将每个参数的类型作为其声明的非限定版本类型

该函数f使用不合格的参数调用(因此,使用正确类型的参数),并且它的所有声明都是兼容类型(根据问题中的引用):函数调用是明确定义的。(如果标准中没有任何内容使其明确未定义。我认为没有。)

于 2015-03-03T18:08:07.447 回答
0

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++andx--因为它可以假设没有其他代码会检查x这两个操作之间的值。但是,该volatile声明将强制编译器假定在和之间执行x之后检查的代码,从而观察当时保持的值。setjmpx++x--x

可以设计一个平台调用约定,其中一个“聪明的”优化器对函数的定义一无所知,除了它没有volatile在参数上使用限定符这一事实之外,它将能够生成在存在时不允许的代码有这样的限定符,但即使在这样的平台上,只看到函数原型缺少volatile限定符的编译器也没有根据假设其定义不包含限定符。

于 2018-08-29T15:41:07.527 回答
-1

在参数上使用 'volatile' 意味着每次使用参数时都重新读取参数,而不仅仅是使用以前读取的值。

这对于传递的参数“通常”是无用的。

'volatile' 的时间是某些东西可以与代码执行异步更改的时间,例如在中断或 I/O 值中修改的东西。

传递的参数是副本,不会异步更改。

“限制”是编码人员对编译器的承诺,编译器可以忽略某些可能的问题,

例如'我,编码员,承诺此调用 memcpy() 的内存区域不会重叠。

因此,只需在相关时使用它们,否则不要使用它们。

于 2015-03-03T01:07:08.017 回答