When you use an array like a in an expression, it usually goes through “pointer decay” which means that you get a pointer to the first element of the array.
So, a has type int[3][4], and it decays to int (*)[4]. When you write a+1, you get the address of a[1]… which is sizeof(*a) bytes after a, which is sizeof(int)*4, which is 8 on your system.
When you use &, pointer decay does not happen. &a is not an array (it is already a pointer), so when you write &a+1, you get the address “one past the end” of a… which is sizeof(a) bytes after a, which is sizeof(int)*12, or 24 bytes on your system.
Note that, strictly speaking, the correct way to print pointers is with %p.
printf("%p\n", a);
printf("%p, %p\n", a+1, &a+1);
(Technically, you also have to cast to char * or void * but that’s hardly ever important.)