Say you have these 2 funcs, like you posted:
void func2(float** floats) {
*floats[1] = 1.0;
}
void func(float** floats) {
func2(floats);
}
Then you have the following code:
float* floats;
func(&floats);
What happens? First, floats is a float *, and since it's not assigned, it points to a random memory location, say 0xBADD. If you write to here, you're liable to screw something up.
The address of floats, &floats is another memory location pointing to where it is on the stack, say 0x1000. This is passed to func, which is then verbatim passed to func2.
What does func2 do? It tries to write something in the location *floats[1]. I'm not sure what happens first, the bracket or the star, so let's take both cases:
*(floats[1]): First you find what floats[1] is. Since floats is the memory location of the initial floats variable, this is 0x1000. floats[1] would be 0x1004, since pointers are 4 bytes wide assuming a 32-bit system. Then you try to dereference this pointer, which is the value of whatever is in memory location 0x1004. This could be anything, like 0xBAD2, so trying to write a float value there most likely causes a segfault.
(*floats)[1]. Here you first dereference floats, which gets you 0xBADD, of type float *. You then try to write a value into 0xBADD + 4, or 0xBAE1. This is memory that hasn't been allocated for you, so writing there will likely cause a segfault.
Either way, you're messing with memory that isn't yours, thus leading to a segfault.