数组函数参数声明中的静态关键字

St.*_*rio 6 c assembly gcc x86-64 language-lawyer

以下是6.7.6.3/7含义的解释:

如果关键字static也出现在[]的数组类型推导中,则对于函数的每次调用,相应实际参数的值应提供对数组第一个元素的访问,该元素的大小至少与size指定的数量相同表达。

含义还不清楚。我运行了以下示例:

main.c

#include "func.h"

int main(void){
    char test[4] = "123";
    printf("%c\n", test_func(2, test));
}
Run Code Online (Sandbox Code Playgroud)

以及2种不同的实现test_func

  1. 静态版本

func.h

char test_func(size_t idx, const char[const static 4]);
Run Code Online (Sandbox Code Playgroud)

func.c

char test_func(size_t idx, const char arr[const static 4]){
    return arr[idx];
} 
Run Code Online (Sandbox Code Playgroud)
  1. 非静态版本

func.h

char test_func(size_t idx, const char[const 4]);
Run Code Online (Sandbox Code Playgroud)

func.c

char test_func(size_t idx, const char arr[const 4]){
    return arr[idx];
}
Run Code Online (Sandbox Code Playgroud)

gcc 7.4.0 -O3在这两种情况下,我都检查了用该函数编译的汇编代码,结果完全相同:

拆卸功能

(gdb) disas main
sub    rsp,0x18
mov    edi,0x2
lea    rsi,[rsp+0x4]
mov    DWORD PTR [rsp+0x4],0x333231
mov    rax,QWORD PTR fs:0x28
mov    QWORD PTR [rsp+0x8],rax
xor    eax,eax
call   0x740 <test_func>
[...]

(gdb) disas test_func 
movzx  eax,BYTE PTR [rsi+rdi*1]
ret  
Run Code Online (Sandbox Code Playgroud)

您能否举一个例子,与非静态对应项相比,static关键字具有一些好处(或根本没有区别)?

dpi*_*dpi 11

这是一个static实际上有所作为的示例:

unsigned foo(unsigned a[2])
{
    return a[0] ? a[0] * a[1] : 0;
}
Run Code Online (Sandbox Code Playgroud)

clang(对于x86-64,带有-O3)将其编译为

foo:
        mov     eax, dword ptr [rdi]
        test    eax, eax
        je      .LBB0_1
        imul    eax, dword ptr [rdi + 4]
        ret
.LBB0_1:
        xor     eax, eax
        ret
Run Code Online (Sandbox Code Playgroud)

但是用替换功能参数后unsigned a[static 2],结果很简单

foo:
        mov     eax, dword ptr [rdi + 4]
        imul    eax, dword ptr [rdi]
        ret
Run Code Online (Sandbox Code Playgroud)

条件分支不是必需的,因为a[0] * a[1]评估a [0]是否为零可得出正确的结果。但是,如果没有static关键字,编译器将无法假定可以访问a [1],因此必须检查a [0]。

当前只有clang进行此优化。在两种情况下,ICC和gcc都会生成相同的代码。

  • @ St.Antario:是的,foo(unsigned a [2])与foo(unsigned * a)完全等效,并且允许a指向未映射页面之前的最后一个int。foo(unsigned a [static 2])是不同的:整个数组对象必须在那里。(但是函数中的`a`类型仍然是`unsigned * a`,不是实际的数组,因此您不能使用`sizeof(a)`来获取数组大小。https://godbolt.org/z / 6fz3jQ) (3认同)

Lun*_*din 6

根据我的经验,编译器并没有太多使用它,但是一种用法是,编译器可以假设参数(数组衰减为指针)不是NULL

有了这个功能,gcc和clang(x86)会在-O3以下位置产生相同的机器代码:

int func (int a[2])
{
  if(a)
    return 1;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

拆卸:

func:
        xor     eax, eax
        test    rdi, rdi
        setne   al
        ret
Run Code Online (Sandbox Code Playgroud)

将参数更改int a[static 2]为时,gcc会提供与以前相同的输出,但是clang可以做得更好:

func: 
        mov     eax, 1
        ret
Run Code Online (Sandbox Code Playgroud)

由于clang意识到a永远不能为NULL,因此可以跳过检查。