As long as you are working with single-dimensional arrays only, the above declarations are all equivalent. The last one though
void f(int i, int a[static i])
has an extra effect. It is equivalent to the previous ones in terms of the parameter types, but also tells the compiler that it can rely on a parameter pointing to an array of at least i elements (which can be used in optimizations).
You are also forgetting another new declaration
void f(int i, int a[const])
This one actually does have an effect even in case of a single-dimensional array. It is equivalent to
void f(int i, int *const a)
although some might argue that const-qualifications on function parameters are useless. Before it was impossible to const-qualify the pointer the array parameter "decays" to when using the [] syntax for the declaration.
The * (as well as i) between the [] begins to matter only when it is used between the second (or greater) pair of [] in multi-dimensional array declaration. In essence, it is just like it has always been: array size in the parameter declaration always mattered only between the second or further pair of []. The * is used in prototype declarations for VLA parameters, when the size value is not named explicitly. For example, you can declare
void bar(int n, int m, int a[n][m]);
and the compiler will know that a is a VLA since the sizes are not constants. But if you prefer not to name parameters in prototypes, how are you going to tell the compiler that a is a VLA? That's when * helps
void bar(int, int, int a[*][*]);