11

A very simple program in C:

#include <stdio.h>
#include <stdlib.h>

void process(int array[static 5]){
    int i;
    for(i=0; i<5; i++)
        printf("%d ", array[i]);
    printf("\n");
}

int main(){

    process((int[]){1,2,3});
    process(NULL);

    return 0;
}

I compile it: gcc -std=c99 -Wall -o demo demo.c

It does compile and when I run it, it crashes (quite predictable).

Why? What is the purpose of the static keyword in array parameter (whats the name of this construct btw?) ?

Joshua MN
  • 1,486
  • 2
  • 13
  • 26
  • 3
    Note that Clang uses `int array[static 1]` in function argument position as an idiom to mean “non-NULL pointer” and **will** warn if you pass NULL to the function. But no compiler that I know off goes the extra step of tracking sizes, because although they are often static, they can vary at run-time in the general case. But an idiom to express that a function expects a non-NULL pointer is already a good thing, right? – Pascal Cuoq Aug 21 '13 at 16:10
  • 3
    I disagree about the vote to close as duplicate. The purported duplicate explains what `static` in `int array[static 5]` means and how it could be expected to be used by compilers to warn. This question, from someone who **already** understand what `int array[static 5]` means, asks why GCC does not warn despite the `static` keyword being used correctly. – Pascal Cuoq Aug 21 '13 at 16:14
  • @PascalCuoq exactly! :) – Joshua MN Aug 21 '13 at 16:16
  • 1
    @JoshuaMN Then the answer is that compilers do not always warn when they should, I am afraid :) I can sell you an annotation system for C where you can express the same thing in a different syntax and make it warn, but it would have to be written `requires \valid(array+(0..4));` in that system. – Pascal Cuoq Aug 21 '13 at 16:19
  • @PascalCuoq what's that? – Joshua MN Aug 22 '13 at 06:22
  • 1
    ACSL is a specification language for C functions. It follows the philosophy of http://en.wikipedia.org/wiki/Design_by_contract : each function places the onus on its callers to call it in only the right conditions; in exchange it executes safely and provides some guarantees about its results. “requires” introduces the pre-condition part of a function contract. Some examples of contracts are in http://www.fokus.fraunhofer.de/de/quest/_download_quest/_projekte/acsl_by_example.pdf . There is mostly only one tool that uses ACSL yet, but that tool gathers several analyzers: http://frama-c.com/ – Pascal Cuoq Aug 22 '13 at 06:38
  • Actually, there are two tools that use ACSL (I keep forgetting and I hope they won't mind if they see this). Microsoft's VCC uses an annotation language as close to ACSL as was possible while retaining the possibility for VCC's authors to research the directions that they intended: http://research.microsoft.com/en-us/projects/vcc/ – Pascal Cuoq Aug 22 '13 at 06:46
  • Compile with clang. I will get the expected warning. – Jérôme Pouiller Sep 21 '21 at 12:10
  • It also works with gcc 11. See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=50584 – Jérôme Pouiller Sep 21 '21 at 12:27
  • "clang uses `int array[static 1]` as an idiom to mean non-NULL pointer and **will** warn if you pass NULL to the function." This only holds true if you pass `NULL` literally. Try `int *null = NULL; process(null);` instead and you'll see that no such warning is generated in either clang or gcc, even if you dial the warnings all the way up. Nobody's gonna pass NULL on purpose. It'll happen by accident, e.g. passing malloc's return value without checking if it's null. The compiler will not warn about this either. – Braden Best Aug 04 '22 at 17:01

2 Answers2

16

The static there is an indication (a hint — but not more than a hint) to the optimizer that it may assume there is a minimum of the appropriate number (in the example, 5) elements in the array (and therefore that the array pointer is not null too). It is also a directive to the programmer using the function that they must pass a big enough array to the function to avoid undefined behaviour.

ISO/IEC 9899:2011

§6.7.6.2 Array declarators

Constraints
¶1 In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.

§6.7.6.3 Function declarators (including prototypes)

¶7 A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. 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.


Your code crashes because if you pass a null pointer to a function expecting an array (that is guaranteed to be the start of an array of 5 elements). You are invoking undefined behaviour and crash is an eminently sensible way of dealing with your mistake.

It is more subtle when you pass an array of 3 integers to a function that's guaranteed an array of 5 integers; again, you invoke undefined behaviour and the results are unpredictable. A crash is relatively unlikely; spurious results are very probable.

In effect, the static in this context has two separate jobs — it defines two separate contracts:

  1. It tells the user of the function that they must provide an array of at least 5 elements (and if they do not, they will invoke undefined behaviour).
  2. It tells the optimizer that it may assume a non-null pointer to an array of at least 5 elements and it may optimize accordingly.

If the user of the function violates the requirements of the function, all hell may break loose ('nasal demons' etc; generally, undefined behaviour).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    `process((int[]){1,2,3});` also should have 5 elements. – haccks Aug 21 '13 at 16:07
  • That's the real problem ... as I was typing in my answer... you beat me to it :) – Jason R. Mick Aug 21 '13 at 16:11
  • @Jonathan ... would a crash still occur when you access `array[3]`? Wouldn't that result in a segfault for the the three member array? – Jason R. Mick Aug 21 '13 at 16:15
  • So passing an array with 6 elements would work? – Jiminion Aug 21 '13 at 16:15
  • Yes, that is correct, Jim. – Jason R. Mick Aug 21 '13 at 16:16
  • Accessing `array[3]` is undefined behaviour because the actual array only has 3 elements. The address is valid; you may use `&array[3]` for an array such as `int array[3];`. But you may not access the element `array[3]` without invoking undefined behaviour. What happens when you invoke undefined behaviour is undefined — a crash is one option; accessing some memory that doesn't cause a crash (but is not part of the array) is more probable. @Jim: yes, passing an array with 6 elements would work. – Jonathan Leffler Aug 21 '13 at 16:18
  • @JonathanLeffler so `static` (as .. `restrict` maybe?) is **just a hint** ? – Joshua MN Aug 21 '13 at 16:19
  • Correct — it is a hint. – Jonathan Leffler Aug 21 '13 at 16:22
  • 1
    @JoshuaMN Actually, both this use of `static` and `restrict` can be used by the compiler as a way to optimize the inside of the function and as a way to ensure that callers call it properly (issuing warnings). Compiler makers are obsessed with performance to the detriment of usability and only use them for the former, but the latter is possible with both constructs. In the case of restrict I once discussed it at http://blog.frama-c.com/index.php?post/2012/07/25/The-restrict-qualifier-as-an-element-of-specification and http://blog.frama-c.com/index.php?post/2012/08/02/restrict-not-modular – Pascal Cuoq Aug 21 '13 at 16:24
  • Ok, so please stress the part saying it's a hint, that no warnings may be issued etc. and I'll accept – Joshua MN Aug 21 '13 at 16:29
1

Your code is correct (and actually recommended... see the C99, N1124/1256, clause 6.7.5.3-7 (see Jonathan's full text below):

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.

The error is that your array definition -- you allocate it to hold 3 elements, but then you call a function that requires five elements (via the [static 5]), triggering a crash.

Jason R. Mick
  • 5,177
  • 4
  • 40
  • 69
  • ok, so whats the purpose of `[static 5]` if I can call it with 3 elements? – Joshua MN Aug 21 '13 at 16:18
  • 1
    @JoshuaMN; It is telling you that there should be minimum of 5 elements in the array. When calling it with 3 elements, it will take other two as garbage value (invoking UB). – haccks Aug 21 '13 at 16:25
  • Unfortunately, you missed the real reason why the OP's program crashes (dereferencing of `NULL`). The `[static 5]` in combination with reading a three element array on the stack does not have the power to crash the program, it only has the power to print garbage. – cmaster - reinstate monica Apr 01 '15 at 20:17
  • @cmaster-reinstatemonica It has the power to do anything. Undefined behavior is undefined. It may print garbage, it may crash, it may do something else. – user16217248 Jul 12 '22 at 05:07
  • @user16217248 In principle, that's correct. More precisely, the behavior depends on what the compiler chooses to do with the undefined behavior. And there are two general modes for that: 1) The compiler sees UB and optimizes the code away. 2) The compiler is oblivious to the UB, and acts as if there were no problem at all. If the first case applied to the OP, both `process()` calls would have been optimized away, rendering `main()` into a noop that executes without crash. So case 2 applies: The first call prints additional garbage and the second crashes the process due to the passed `NULL`. – cmaster - reinstate monica Jul 12 '22 at 07:44