8

Here is the explanation of what it means 6.7.6.3/7:

If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

It is not quite clear what it means. I ran the following example:

main.c

#include "func.h"

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

And 2 different implementations of the test_func:

  1. static version

func.h

char test_func(size_t idx, const char[const static 4]);

func.c

char test_func(size_t idx, const char arr[const static 4]){
    return arr[idx];
} 
  1. non-static version

func.h

char test_func(size_t idx, const char[const 4]);

func.c

char test_func(size_t idx, const char arr[const 4]){
    return arr[idx];
}

I checked the assembly code compiled with gcc 7.4.0 -O3 of the function in both of the cases and it turned out to be completely identical:

Disassembly of the functions

(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  

Can you give an example where the static keyword gives some benefits (or any differences at all) comparing to non-static counterpart?

4
  • 2
    I'd say the point is to catch smaller arguments so I would expect it to throw a compilation error for test[3] ... except it doesn't. Maybe the compiler is not required to provide diagnostic or just gcc doesn't. clang does give a warning: array argument is too small; contains 3 elements, callee requires at least 4 [-Warray-bounds] PS: has nothing to do with assembly, it's a compilation time thing. Commented Jun 19, 2019 at 14:26
  • @Jester Just checked it in case of passing a smaller array. As you noted, adding -Warray-bounds to the compiler options does not give any warning. Maybe this is a gcc bug... ? Commented Jun 19, 2019 at 14:36
  • 1
    It's a bug if the standard says diagnostic is required. I do not have access to the standard, so I can't tell. But even clang is only providing a warning. Commented Jun 19, 2019 at 14:39
  • 1
    For the code you have provided, the static keyword is useless anyway: The static keyword allows the compiler to assume that the array has at least as many elements as indicated, but if you access an index, the compiler may also assume the index is valid, because accessing out of bounds would be undefined behavior. To construct a case where the feature could be useful, the access would have to be to a known index, but conditional on some run-time predicate. However, the feature is apparently not used much, so gcc doesn't make use of the freedom it offers. Commented Jun 19, 2019 at 16:32

2 Answers 2

11

Here is an example where static actually makes a difference:

unsigned foo(unsigned a[2])
{
    return a[0] ? a[0] * a[1] : 0;
}

clang (for x86-64, with -O3) compiles this to

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

But after replacing the function parameter with unsigned a[static 2], the result is simply

foo:
        mov     eax, dword ptr [rdi + 4]
        imul    eax, dword ptr [rdi]
        ret

The conditional branch is not necessary because a[0] * a[1] evaluates to the correct result whether a[0] is zero or not. But without the static keyword, the compiler cannot assume that a[1] can be accessed, and thus has to check a[0].

Currently only clang does this optimization; ICC and gcc produce the same code in both cases.

Sign up to request clarification or add additional context in comments.

5 Comments

Nice job finding a case where a compiler actually does take advantage of the guarantee that the whole array is there and readable. Last time I played with [static size] on Godbolt I only found missed optimizations, IIRC, but I don't remember what exactly I tried.
The interesting thing is that this seems like a case where actual code would not want to make that guarantee. It seems like the zero element is a sentinel at the end of a dynamic-sized array, and you normally wouldn't want to allow access past it.
And note that this probably wouldn't be a valid optimization for a float type, because of infinities and NaN. It takes advantage of a specific integer identity.
@PeterCordes So the point of static is to make sure we don't get out of bound in case we access an array member with an index lesser than the size expression value?
@St.Antario: yes, foo(unsigned a[2]) is exactly equivalent to foo(unsigned *a), and a is allowed to be pointing at the last int before an unmapped page. foo(unsigned a[static 2]) is different: the whole array object must be there. (But a's type inside the function is still unsigned *a, not an actual array so you can't use sizeof(a) to get the array size. godbolt.org/z/6fz3jQ)
7

This isn't used much by compilers in my experience, but one use is that the compiler can assume that the (array decayed into pointer) parameter is not NULL.

Given this function, both gcc and clang (x86) produce identical machine code at -O3:

int func (int a[2])
{
  if(a)
    return 1;
  return 0;
}

Disassembly:

func:
        xor     eax, eax
        test    rdi, rdi
        setne   al
        ret

When changing the parameter to int a[static 2], gcc gives the same output as before, but clang does a better job:

func: 
        mov     eax, 1
        ret

Since clang realizes that a can never be NULL, so it can skip the check.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.