I want to explicitly state the important part that is implied in other answers:
The malloc does not know what it allocates memory for.
It receives the number of bytes as an input and returns a pointer, that has at least that many bytes addressable after it.
On my compiler/machine combination sizeof(Detection) is 88. So malloc just allocates 88-ish bytes in heap and returns a pointer to it.
It is the programmer's responsibility to interpret that memory in certain way (and compiler's responsibility to help with the layout the struct members in memory in a correct fashion and generate proper code that given the pointer to memory and a member name it would calculate proper offset inside that memory to access that member data) - in your code you assign that address to a pointer to the Detection struct. You could also try assigning it to a pointer to another struct and it might or might not work (as in program crashes and/or wipes all the data on your computer) depending on the struct size and the amount of memory allocated.
So, malloc, does not know that it allocates memory for a struct containing arrays. It just allocates continuous region of memory, that if coded properly (i.e. proper size passed to malloc) would be enough to contain a struct of type Detection.
We can modify your code a bit (I cheated a bit, since on my machines sizes of long long and double happen to be the same, don't do that):
#include <stdlib.h>
#include <stdio.h>
typedef struct {
int id;
double c[2];
double p[4][2];
} Detection;
int main()
{
size_t size = sizeof(Detection);
printf("sizeof(Detection) = %u\n", size);
Detection *detection = malloc(size);
detection->id = 0x01020304;
*(long long *)(&detection->c[0]) = 0x1011121314151617;
*(long long *)(&detection->c[1]) = 0x18191A1B1C1D1E1F;
*(long long *)(&detection->p[0][0]) = 0x2021222324252627;
*(long long *)(&detection->p[0][1]) = 0x28292A2B2C2D2E2F;
*(long long *)(&detection->p[1][0]) = 0x3031323334353637;
*(long long *)(&detection->p[1][1]) = 0x38393A3B3C3D3E3F;
*(long long *)(&detection->p[2][0]) = 0x4041424344454647;
*(long long *)(&detection->p[2][1]) = 0x48494A4B4C4D4E4F;
*(long long *)(&detection->p[3][0]) = 0x5051525354555657;
*(long long *)(&detection->p[3][1]) = 0x58595A5B5C5D5E5F;
}
Then run it in the debugger to see the memory layout, which in my case is (starting from the third line the 0x01097018 address):
0x01097008 01 00 00 00 58 00 00 00 4e 00 00 00 fd fd fd fd ....X...N...ýýýý
======== detection starts here =========
0x01097018 04 03 02 01 cd cd cd cd 17 16 15 14 13 12 11 10 ....ÍÍÍÍ........
0x01097028 1f 1e 1d 1c 1b 1a 19 18 27 26 25 24 23 22 21 20 ........'&%$#"!
0x01097038 2f 2e 2d 2c 2b 2a 29 28 37 36 35 34 33 32 31 30 /.-,+*)(76543210
0x01097048 3f 3e 3d 3c 3b 3a 39 38 47 46 45 44 43 42 41 40 ?>=<;:98GFEDCBA@
0x01097058 4f 4e 4d 4c 4b 4a 49 48 57 56 55 54 53 52 51 50 ONMLKJIHWVUTSRQP
0x01097068 5f 5e 5d 5c 5b 5a 59 58
From the values in memory you can easily see how the struct is laid out in memory, at can notice that arrays are embedded into that memory layout.
As a consequence, free also has absolutely no idea what type of struct sits on the other end of the pointer passed to it. It simply receives an address that was [allegedly] previously returned by malloc (a void *) and knows how to deallocate it as a chunk of memory (not as a struct of certain type). There is no kind of "destruction" happening other than marking the memory as "not used".
I added an additional 16 bytes of memory before the address returned by the malloc to demonstrate how free knows about how many memory to deallocate (this is totally implementation dependent, by the way, and you should not rely on it in any way). You may notice that 58 in the first line which translates to decimal 88 - it is the size of the memory allocated
One can easily pass size + 42 to a malloc, it would still be correct program, it just would waste the memory.